มันคุ้มค่ากับความพยายามสำหรับฉัน แต่อย่างใดดังนั้นฉันจะเสนอวิธีแก้ปัญหาที่ยากและสง่างามที่สุดสำหรับใครก็ตามที่อาจสนใจ วิธีแก้ปัญหาของฉันคือการใช้ min-max แบบมัลติเธรดในอัลกอริทึมการส่งผ่านเดียวใน C ++ และใช้สิ่งนี้เพื่อสร้างโมดูลส่วนขยาย Python ความพยายามนี้ต้องใช้ค่าใช้จ่ายเล็กน้อยในการเรียนรู้วิธีใช้ Python และ NumPy C / C ++ API และที่นี่ฉันจะแสดงโค้ดและให้คำอธิบายและการอ้างอิงเล็ก ๆ น้อย ๆ สำหรับผู้ที่ต้องการลงเส้นทางนี้
หลายเธรดต่ำสุด / สูงสุด
ที่นี่ไม่มีอะไรน่าสนใจเกินไป length / workers
อาร์เรย์ถูกแบ่งออกเป็นชิ้นขนาด ค่าต่ำสุด / สูงสุดจะคำนวณสำหรับแต่ละกลุ่มใน a 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
โมดูลส่วนขยาย Python
นี่คือสิ่งที่เริ่มน่าเกลียด ... วิธีหนึ่งในการใช้โค้ด C ++ ใน Python คือการใช้โมดูลส่วนขยาย โมดูลนี้สามารถสร้างและติดตั้งได้โดยใช้distutils.core
โมดูลมาตรฐาน คำอธิบายที่สมบูรณ์ของสิ่งที่สร้างความนี้จะกล่าวถึงในเอกสารหลาม: https://docs.python.org/3/extending/extending.html หมายเหตุ:มีวิธีอื่น ๆ ในการให้ได้ผลลัพธ์ที่คล้ายกันโดยอ้างถึงhttps://docs.python.org/3/extending/index.html#extending-index :
คู่มือนี้ครอบคลุมเฉพาะเครื่องมือพื้นฐานสำหรับการสร้างส่วนขยายที่ให้ไว้เป็นส่วนหนึ่งของ CPython เวอร์ชันนี้ เครื่องมือของบุคคลที่สามเช่น Cython, cffi, SWIG และ Numba นำเสนอทั้งวิธีการที่ง่ายและซับซ้อนกว่าในการสร้างส่วนขยาย C และ C ++ สำหรับ Python
โดยพื้นฐานแล้วเส้นทางนี้น่าจะเป็นวิชาการมากกว่าปฏิบัติ เมื่อพูดอย่างนั้นสิ่งที่ฉันทำต่อไปคือติดกับบทช่วยสอนสร้างไฟล์โมดูล นี่เป็นเอกสารสำเร็จรูปสำหรับผู้ที่ไม่สนใจที่จะรู้ว่าจะทำอย่างไรกับโค้ดของคุณและสร้างโมดูล Python จากมัน ก่อนที่จะดำเนินการใด ๆ คุณควรสร้างสภาพแวดล้อมเสมือน Python เพื่อที่คุณจะได้ไม่ก่อให้เกิดมลพิษต่อแพ็คเกจระบบของคุณ (ดู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); }
ในไฟล์นี้มีการใช้ Python อย่างมากเช่นเดียวกับ NumPy API สำหรับข้อมูลเพิ่มเติมโปรดดูที่https://docs.python.org/3/c-api/arg.html#c.PyArg_ParseTupleและสำหรับ NumPy : https://docs.scipy.org/doc/numpy/reference/c-api.array.html
การติดตั้งโมดูล
สิ่งต่อไปที่ต้องทำคือใช้ distutils เพื่อติดตั้งโมดูล ต้องใช้ไฟล์ติดตั้ง:
# 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
จากสภาพแวดล้อมเสมือนของคุณ
การทดสอบโมดูล
สุดท้ายเราสามารถทดสอบเพื่อดูว่าการใช้งาน C ++ มีประสิทธิภาพดีกว่าการใช้ 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.5 เท่าและไม่รวมมัลติเธรด ผลลัพธ์ที่ฉันทำได้ค่อนข้างสมเหตุสมผลฉันคาดหวังว่าค่าใช้จ่ายของเธรดและจะครองเวลาจนกว่าอาร์เรย์จะมีขนาดใหญ่มากเมื่อถึงจุดนั้นการเพิ่มประสิทธิภาพจะเริ่มเข้าใกล้std::thread::hardware_concurrency
x เพิ่มขึ้น
สรุป
แน่นอนว่ามีช่องว่างสำหรับการปรับแต่งเฉพาะแอปพลิเคชันให้กับโค้ด NumPy บางรหัสโดยเฉพาะอย่างยิ่งในเรื่องของมัลติเธรด ไม่ว่าจะคุ้มค่ากับความพยายามหรือไม่นั้นก็ไม่ชัดเจนสำหรับฉัน แต่ดูเหมือนว่าจะเป็นการออกกำลังกายที่ดี (หรือบางอย่าง) ฉันคิดว่าบางทีการเรียนรู้ "เครื่องมือของบุคคลที่สาม" เช่น Cython อาจเป็นการใช้เวลาที่ดีกว่า แต่ใครจะรู้
amax
และamin