এটি যাইহোক আমার জন্য প্রচেষ্টা মূল্যবান ছিল, তাই আমি আগ্রহী হতে পারে তার জন্য আমি এখানে সবচেয়ে কঠিন এবং কমপক্ষে মার্জিত সমাধানের প্রস্তাব করব। আমার সমাধানটি হ'ল সি ++ এ এক পাসের অ্যালগরিদমে একাধিক-থ্রেডেড মিনি-ম্যাক্স প্রয়োগ করা এবং পাইথন এক্সটেনশন মডিউলটি তৈরি করতে এটি ব্যবহার করুন। পাইথন এবং নুমপি সি / সি ++ এপিআই কীভাবে ব্যবহার করতে হয় তা শেখার জন্য এই প্রয়াসটির জন্য কিছুটা ওভারহেডের প্রয়োজন হয় এবং আমি এখানে কোডটি দেখাব এবং যে কেউ এই পথে যেতে চাইবে তার জন্য কিছু ছোট ব্যাখ্যা এবং রেফারেন্স দেব।
বহু-থ্রেডযুক্ত ন্যূনতম / সর্বোচ্চ
এখানে খুব আকর্ষণীয় কিছু নেই। অ্যারের আকারের অংশে বিভক্ত length / workers
। সর্বনিম্ন / সর্বোচ্চটি এ-এর প্রতিটি future
অংশের জন্য গণনা করা হয় , যা পরে বিশ্বব্যাপী ন্যূনতম / সর্বোচ্চের জন্য স্ক্যান করা হয়।
// mt_np.cc
//
// multi-threaded min/max algorithm
#include <algorithm>
#include <future>
#include <vector>
namespace mt_np {
/*
* Get {min,max} in interval [begin,end)
*/
template <typename T> std::pair<T, T> min_max(T *begin, T *end) {
T min{*begin};
T max{*begin};
while (++begin < end) {
if (*begin < min) {
min = *begin;
continue;
} else if (*begin > max) {
max = *begin;
}
}
return {min, max};
}
/*
* get {min,max} in interval [begin,end) using #workers for concurrency
*/
template <typename T>
std::pair<T, T> min_max_mt(T *begin, T *end, int workers) {
const long int chunk_size = std::max((end - begin) / workers, 1l);
std::vector<std::future<std::pair<T, T>>> min_maxes;
// fire up the workers
while (begin < end) {
T *next = std::min(end, begin + chunk_size);
min_maxes.push_back(std::async(min_max<T>, begin, next));
begin = next;
}
// retrieve the results
auto min_max_it = min_maxes.begin();
auto v{min_max_it->get()};
T min{v.first};
T max{v.second};
while (++min_max_it != min_maxes.end()) {
v = min_max_it->get();
min = std::min(min, v.first);
max = std::max(max, v.second);
}
return {min, max};
}
}; // namespace mt_np
পাইথন এক্সটেনশন মডিউল
এখানেই জিনিসগুলি কুৎসিত হতে শুরু করে ... পাইথনে সি ++ কোড ব্যবহারের এক উপায় হ'ল একটি এক্সটেনশন মডিউল প্রয়োগ করা। এই মডিউলটি distutils.core
স্ট্যান্ডার্ড মডিউলটি ব্যবহার করে ইনস্টল করা যায় । পাইথন ডকুমেন্টেশনে এটিতে কী যুক্ত রয়েছে তার একটি সম্পূর্ণ বিবরণ: https://docs.python.org/3/extending/extending.html । দ্রষ্টব্য: https://docs.python.org/3/extending/index.html#extending-index উদ্ধৃত করার জন্য অনুরূপ ফলাফল পাওয়ার অন্যান্য উপায়গুলি অবশ্যই রয়েছে :
এই গাইডটি কেবল সিপিথনের এই সংস্করণটির অংশ হিসাবে সরবরাহ করা এক্সটেনশান তৈরির প্রাথমিক সরঞ্জামগুলিকে coversেকে রেখেছে। সিথন, সিএফআই, এসডাব্লুআইজি এবং নুম্বার মতো তৃতীয় পক্ষের সরঞ্জামগুলি পাইথনের জন্য সি এবং সি ++ এক্সটেনশন তৈরিতে উভয় সহজ এবং আরও পরিশীলিত পদ্ধতির প্রস্তাব দেয়।
মূলত, এই রুটটি সম্ভবত ব্যবহারিকের চেয়ে বেশি শিক্ষামূলক। এর সাথে বলা হচ্ছে যে, আমি এরপরে যা করলাম তা হ'ল টিউটোরিয়ালটির খুব কাছে গিয়ে মডিউল ফাইল তৈরি করুন। আপনার কোডটির সাথে কী করবেন এবং এটি থেকে একটি পাইথন মডিউল তৈরি করতে ডিস্টুইলেটগুলির জন্য এটি মূলত বয়লারপ্লেট। এর যে কোনও কিছু করার আগে পাইথন ভার্চুয়াল পরিবেশ তৈরি করা বুদ্ধিমানের কাজ যাতে আপনি আপনার সিস্টেম প্যাকেজগুলিকে কলুষিত না করেন ( https://docs.python.org/3/library/venv.html#module-venv দেখুন )।
মডিউল ফাইলটি এখানে:
// mt_np_forpy.cc
//
// C++ module implementation for multi-threaded min/max for np
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <python3.6/numpy/arrayobject.h>
#include "mt_np.h"
#include <cstdint>
#include <iostream>
using namespace std;
/*
* check:
* shape
* stride
* data_type
* byteorder
* alignment
*/
static bool check_array(PyArrayObject *arr) {
if (PyArray_NDIM(arr) != 1) {
PyErr_SetString(PyExc_RuntimeError, "Wrong shape, require (1,n)");
return false;
}
if (PyArray_STRIDES(arr)[0] != 8) {
PyErr_SetString(PyExc_RuntimeError, "Expected stride of 8");
return false;
}
PyArray_Descr *descr = PyArray_DESCR(arr);
if (descr->type != NPY_LONGLTR && descr->type != NPY_DOUBLELTR) {
PyErr_SetString(PyExc_RuntimeError, "Wrong type, require l or d");
return false;
}
if (descr->byteorder != '=') {
PyErr_SetString(PyExc_RuntimeError, "Expected native byteorder");
return false;
}
if (descr->alignment != 8) {
cerr << "alignment: " << descr->alignment << endl;
PyErr_SetString(PyExc_RuntimeError, "Require proper alignement");
return false;
}
return true;
}
template <typename T>
static PyObject *mt_np_minmax_dispatch(PyArrayObject *arr) {
npy_intp size = PyArray_SHAPE(arr)[0];
T *begin = (T *)PyArray_DATA(arr);
auto minmax =
mt_np::min_max_mt(begin, begin + size, thread::hardware_concurrency());
return Py_BuildValue("(L,L)", minmax.first, minmax.second);
}
static PyObject *mt_np_minmax(PyObject *self, PyObject *args) {
PyArrayObject *arr;
if (!PyArg_ParseTuple(args, "O", &arr))
return NULL;
if (!check_array(arr))
return NULL;
switch (PyArray_DESCR(arr)->type) {
case NPY_LONGLTR: {
return mt_np_minmax_dispatch<int64_t>(arr);
} break;
case NPY_DOUBLELTR: {
return mt_np_minmax_dispatch<double>(arr);
} break;
default: {
PyErr_SetString(PyExc_RuntimeError, "Unknown error");
return NULL;
}
}
}
static PyObject *get_concurrency(PyObject *self, PyObject *args) {
return Py_BuildValue("I", thread::hardware_concurrency());
}
static PyMethodDef mt_np_Methods[] = {
{"mt_np_minmax", mt_np_minmax, METH_VARARGS, "multi-threaded np min/max"},
{"get_concurrency", get_concurrency, METH_VARARGS,
"retrieve thread::hardware_concurrency()"},
{NULL, NULL, 0, NULL} /* sentinel */
};
static struct PyModuleDef mt_np_module = {PyModuleDef_HEAD_INIT, "mt_np", NULL,
-1, mt_np_Methods};
PyMODINIT_FUNC PyInit_mt_np() { return PyModule_Create(&mt_np_module); }
এই ফাইলে পাইথনের পাশাপাশি নম্পপি এপিআইয়ের উল্লেখযোগ্য ব্যবহার রয়েছে, আরও তথ্যের জন্য পরামর্শ নিন: https://docs.python.org/3/c-api/arg.html#c.PyArg_ParseTuple , এবং NumPy এর জন্য : https://docs.scipy.org/doc/numpy/references/c-api.array.html ।
মডিউল ইনস্টল করা হচ্ছে
পরবর্তী কাজটি হ'ল মডিউলটি ইনস্টল করার জন্য ডিস্টুইলেটগুলি ব্যবহার করা। এটির জন্য একটি সেটআপ ফাইল দরকার:
# setup.py
from distutils.core import setup,Extension
module = Extension('mt_np', sources = ['mt_np_module.cc'])
setup (name = 'mt_np',
version = '1.0',
description = 'multi-threaded min/max for np arrays',
ext_modules = [module])
পরিশেষে মডিউলটি ইনস্টল করতে python3 setup.py install
আপনার ভার্চুয়াল পরিবেশ থেকে চালিত করুন।
মডিউল পরীক্ষা করা হচ্ছে
পরিশেষে, আমরা দেখতে পারি যে সি ++ বাস্তবায়ন আসলে NumPy এর নিষ্পাপ ব্যবহারকে ছাপিয়ে যায় কিনা। এটি করার জন্য, এখানে একটি সাধারণ পরীক্ষা স্ক্রিপ্ট:
# timing.py
# compare numpy min/max vs multi-threaded min/max
import numpy as np
import mt_np
import timeit
def normal_min_max(X):
return (np.min(X),np.max(X))
print(mt_np.get_concurrency())
for ssize in np.logspace(3,8,6):
size = int(ssize)
print('********************')
print('sample size:', size)
print('********************')
samples = np.random.normal(0,50,(2,size))
for sample in samples:
print('np:', timeit.timeit('normal_min_max(sample)',
globals=globals(),number=10))
print('mt:', timeit.timeit('mt_np.mt_np_minmax(sample)',
globals=globals(),number=10))
এগুলি করা থেকে প্রাপ্ত ফলাফলগুলি এখানে:
8
********************
sample size: 1000
********************
np: 0.00012079699808964506
mt: 0.002468645994667895
np: 0.00011947099847020581
mt: 0.0020772050047526136
********************
sample size: 10000
********************
np: 0.00024697799381101504
mt: 0.002037393998762127
np: 0.0002713389985729009
mt: 0.0020942929986631498
********************
sample size: 100000
********************
np: 0.0007130410012905486
mt: 0.0019842900001094677
np: 0.0007540129954577424
mt: 0.0029724110063398257
********************
sample size: 1000000
********************
np: 0.0094779249993735
mt: 0.007134920000680722
np: 0.009129883001151029
mt: 0.012836456997320056
********************
sample size: 10000000
********************
np: 0.09471094200125663
mt: 0.0453535050037317
np: 0.09436299200024223
mt: 0.04188535599678289
********************
sample size: 100000000
********************
np: 0.9537652180006262
mt: 0.3957935369980987
np: 0.9624398809974082
mt: 0.4019058070043684
ফলাফলগুলি থ্রেডের পূর্বের চেয়ে আরও কম উত্সাহজনক, যা প্রায় 3.5x স্পিডআপের দিকে নির্দেশ করেছে এবং মাল্টি-থ্রেডিং অন্তর্ভুক্ত করেনি। আমি যে ফলাফলগুলি পেয়েছি তা কিছুটা যুক্তিসঙ্গত, আমি প্রত্যাশা করব যে থ্রেডিংয়ের ওভারহেডটি অ্যারেগুলি খুব বড় না হওয়া পর্যন্ত সময়কে প্রভাবিত করবে, যার পর্যায়ে পারফরম্যান্স বৃদ্ধিটি std::thread::hardware_concurrency
এক্স বর্ধনের দিকে আসতে শুরু করবে ।
উপসংহার
কিছু NumPy কোডে অ্যাপ্লিকেশন নির্দিষ্ট অপ্টিমাইজেশনের জন্য অবশ্যই জায়গা আছে, বিশেষত মাল্টি-থ্রেডিংয়ের ক্ষেত্রে এটি মনে হবে। চেষ্টাটি মূল্যবান কিনা তা আমার কাছে পরিষ্কার নয় তবে এটি অবশ্যই একটি ভাল অনুশীলনের (বা কিছু) বলে মনে হচ্ছে। আমি মনে করি যে সম্ভবত সাইথনের মতো সেই "তৃতীয় পক্ষের সরঞ্জামগুলি" শেখা সময়ের সময়ের আরও ভাল ব্যবহার হতে পারে তবে কে জানে।
amax
amin