揭秘sharedctypes性能

2024-05-17

在 python 中,可以在多个进程之间共享 ctypes 对象。然而我注意到分配这些对象似乎非常昂贵。

考虑以下代码:

from multiprocessing import sharedctypes as sct
import ctypes as ct
import numpy as np

n = 100000
l = np.random.randint(0, 10, size=n)

def foo1():
    sh = sct.RawArray(ct.c_int, l)
    return sh

def foo2():
    sh = sct.RawArray(ct.c_int, len(l))
    sh[:] = l
    return sh

%timeit foo1()
%timeit foo2()

sh1 = foo1()
sh2 = foo2()

for i in range(n):
    assert sh1[i] == sh2[i]

输出是:

10 loops, best of 3: 30.4 ms per loop
100 loops, best of 3: 9.65 ms per loop

有两件事让我困惑:

  • 为什么显式分配和初始化比传递 numpy 数组要快得多?
  • 为什么在 python 中分配共享内存如此昂贵?%timeit np.arange(n)只需要46.4 µs。这些时间之间有几个数量级。

示例代码

我稍微重写了您的示例代码来研究这个问题。这是我着陆的地方,我将在下面的答案中使用它:

so.py:

from multiprocessing import sharedctypes as sct
import ctypes as ct
import numpy as np

n = 100000
l = np.random.randint(0, 10, size=n)


def sct_init():
    sh = sct.RawArray(ct.c_int, l)
    return sh

def sct_subscript():
    sh = sct.RawArray(ct.c_int, n)
    sh[:] = l
    return sh

def ct_init():
    sh = (ct.c_int * n)(*l)
    return sh

def ct_subscript():
    sh = (ct.c_int * n)(n)
    sh[:] = l
    return sh

请注意,我添加了两个不使用共享内存的测试用例(并使用常规的ctypes数组代替)。

timer.py:

import traceback
from timeit import timeit

for t in ["sct_init", "sct_subscript", "ct_init", "ct_subscript"]:
    print(t)
    try:
        print(timeit("{0}()".format(t), setup="from so import {0}".format(t), number=100))
    except Exception as e:
        print("Failed:", e)
        traceback.print_exc()
    print

print()

print ("Test",)
from so import *
sh1 = sct_init()
sh2 = sct_subscript()

for i in range(n):
    assert sh1[i] == sh2[i]
print("OK")

检测结果

使用 Python 3.6a0 运行上述代码的结果(具体来说3c2fbdb https://github.com/python/cpython/commit/3c2fbdb) are:

sct_init
2.844902500975877
sct_subscript
0.9383537038229406
ct_init
2.7903486443683505
ct_subscript
0.978101353161037

Test
OK

有趣的是如果你改变n,结果呈线性缩放。例如,使用n = 100000(大 10 倍),你得到的东西几乎慢了 10 倍:

sct_init
30.57974253082648
sct_subscript
9.48625904135406
ct_init
30.509132395964116
ct_subscript
9.465419146697968

Test
OK

速度差

最后,速度差异在于热循环,该循环被调用以通过从 Numpy 数组复制每个值来初始化数组(l) 到新数组 (sh)。这是有道理的,因为正如我们所指出的,速度与数组大小呈线性关系。

当您将 Numpy 数组作为构造函数参数传递时,执行此操作的函数是Array_init https://github.com/python/cpython/blob/6fd916862e1a93b1578d8eabdefc3979a4d4af62/Modules/_ctypes/_ctypes.c#L4213-L4232。但是,如果您使用分配sh[:] = l,那么就是Array_ass_subscript就可以了 https://github.com/python/cpython/blob/6fd916862e1a93b1578d8eabdefc3979a4d4af62/Modules/_ctypes/_ctypes.c#L4398-L4453.

同样,这里重要的是热循环。让我们看看它们。

Array_init热循环(较慢):

for (i = 0; i < n; ++i) {
    PyObject *v;
    v = PyTuple_GET_ITEM(args, i);
    if (-1 == PySequence_SetItem((PyObject *)self, i, v))
        return -1;
}

Array_ass_subscript热循环(更快):

for (cur = start, i = 0; i < otherlen; cur += step, i++) {
    PyObject *item = PySequence_GetItem(value, i);
    int result;
    if (item == NULL)
        return -1;
    result = Array_ass_item(myself, cur, item);
    Py_DECREF(item);
    if (result == -1)
        return -1;
}

事实证明,大部分速度差异在于使用PySequence_SetItem vs. Array_ass_item.

事实上,如果你改变代码Array_init to use Array_ass_item代替PySequence_SetItem (if (-1 == Array_ass_item((PyObject *)self, i, v))),重新编译Python,新结果变为:

sct_init
11.504781467840075
sct_subscript
9.381130554247648
ct_init
11.625461496878415
ct_subscript
9.265848568174988

Test
OK

还是慢了一点,但也慢不了多少。

换句话说,大部分开销是由较慢的热循环引起的,并且主要是由的代码PySequence_SetItem环绕Array_ass_item https://github.com/python/cpython/blob/1364858e6ec7abfe04d92b7796ae8431eda87a7a/Objects/abstract.c#L1584-L1609.

乍一看,这段代码可能看起来开销很小,但事实并非如此。

PySequence_SetItem实际上调用整个Python机制来解决__setitem__方法并调用它。

This 最终在调用中解决Array_ass_item,但只有在大量间接级别之后(直接调用Array_ass_item将完全绕过!)

穿过兔子洞,调用序列看起来有点像这样:

  • s->ob_type->tp_as_sequence->sq_ass_item指着slot_sq_ass_item https://github.com/python/cpython/blob/cca9b8e3ff022d48eeb76d8567f297bc399fec3a/Objects/typeobject.c#L5790-L5803.
  • slot_sq_ass_item呼叫call_method https://github.com/python/cpython/blob/cca9b8e3ff022d48eeb76d8567f297bc399fec3a/Objects/typeobject.c#L1439-L1471.
  • call_method呼叫PyObject_Call https://github.com/python/cpython/blob/1364858e6ec7abfe04d92b7796ae8431eda87a7a/Objects/abstract.c#L2149-L2175
  • 一直如此,直到我们最终到达Array_ass_item..!

换句话说,我们有 C 代码Array_init这就是调用 Python 代码(__setitem__)在热循环中。那很慢。

Why ?

现在,为什么Python使用PySequence_SetItem in Array_init并不是Array_ass_item in Array_init?

这是因为如果这样做的话,它将绕过 Python 领域中向开发人员公开的钩子。

确实,你can拦截呼叫sh[:] = ...通过子类化数组并覆盖__setitem__ (__setslice__在Python 2)。它将被调用一次,并带有slice索引的参数。

同样,定义你自己的__setitem__还覆盖构造函数中的逻辑。它将被调用 N 次,并使用整数参数作为索引。

这意味着如果Array_init直接调用Array_ass_item,那么你会失去一些东西:__setitem__将不再在构造函数中调用,并且您将无法再覆盖该行为。

现在我们可以尝试保持更快的速度,同时仍然暴露相同的 Python 钩子吗?

好吧,也许,使用这段代码Array_init而不是现有的热循环:

 return PySequence_SetSlice((PyObject*)self, 0, PyTuple_GET_SIZE(args), args);

使用这个会调用__setitem__ once带有切片参数(在Python 2上,它会调用__setslice__)。我们仍然会执行 Python hooks,但我们只执行一次而不是 N 次。

使用这段代码,性能变为:

sct_init
12.24651838419959
sct_subscript
10.984305887017399
ct_init
12.138383641839027
ct_subscript
11.79078131634742

Test
OK

其他管理费用

我认为其余的开销可能是由于发生的元组实例化造成的打电话时__init__在数组对象上 https://github.com/python/cpython/blob/6fd916862e1a93b1578d8eabdefc3979a4d4af62/Lib/multiprocessing/sharedctypes.py#L66(注意*,以及事实Array_init期望一个元组args)——这大概与n以及。

确实,如果你更换sh[:] = l with sh[:] = tuple(l)在测试用例中,则性能结果变为almost完全相同的。和n = 100000:

sct_init
11.538272527977824
sct_subscript
10.985187001060694
ct_init
11.485244687646627
ct_subscript
10.843198659364134

Test
OK

可能还有一些较小的事情发生,但最终我们正在比较两个截然不同的热循环。没有理由期望它们具有相同的性能。

我认为尝试打电话可能会很有趣Array_ass_subscript from Array_init不过,对于热循环并查看结果!

基线速度

现在,回答第二个问题,关于分配共享内存。

请注意,分配实际上并没有成本shared记忆。正如上面的结果所示,使用与不使用共享内存之间没有显着差异。

查看 Numpy 代码(np.arange is 在这里实施 https://github.com/numpy/numpy/blob/eeba2cbfa4c56447e36aad6d97e323ecfbdade56/numpy/core/src/multiarray/multiarraymodule.c#L2912-L2930),我们终于可以理解为什么它比sct.RawArray: np.arange似乎没有调用Python“用户空间”(即不打电话给PySequence_GetItem or PySequence_SetItem).

这不一定能解释all差异,但您可能想从那里开始调查。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

揭秘sharedctypes性能 的相关文章

随机推荐

  • 处理 fanart.tv Web 服务响应 JSON 和 C#

    我正在尝试使用 fanart tv Webservice API 但有几个问题 我正在使用 Json Net Newtonsoft Json 并通过其他 Web 服务将 JSON 响应直接反序列化为 C 对象 这里的问题是元素名称正在更改
  • 设置 verify_certs=False 但 elasticsearch.Elasticsearch 因证书验证失败而引发 SSL 错误

    self host KibanaProxy 自我端口 443 self user 测试 self password 测试 我需要禁止证书验证 使用选项时它与curl一起使用 k在命令行上 但是 在使用 Elasticsearch pytho
  • GeoJson 世界数据库 [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在开发一个项目 需要使用 d3 js 显示国家和城市的地图 实际上 D3支持GeoJson格式 通常
  • Jenkins 可以检测到任何 svn 用户每次提交代码吗?

    Jenkins 可以检测到任何 svn 用户每次提交代码吗 我想知道每次 Jenkins 提交 svn user 时 有什么方法或 jenkins 插件吗 现在我用svn updateJenkins 中的 cmd 来更新 svn 您可以按照
  • Couchbase v6.0:更新文档内容而不重置文档过期(TTL)值

    我正在使用 Net Couchbase SDK CouchbaseNetClient Package 创建一个新文档 并在执行此操作时设置该文档的到期值 到期 TTL 值设置正确并且工作正常 问题陈述 创建文档后 我需要更新我使用 N1QL
  • 在 VBA Excel 中查找、剪切和插入行以匹配借项和贷项值

    我在 Sheet1 中有以下设置数据 并从第 4 行 A 列开始 其中标题位于第 3 行 No Date Code Name Remarks D e b i t Cr e d i t 1 4 30 2015 004 AB 01 04 15
  • ACCESS_BACKGROUND_LOCATION 不适用于低于 Q (29) 的 Android 版本

    我的应用程序面向 Android API 28 根据文档 https developer android com preview privacy location target android 10 我应该要求ACCESS BACKGROU
  • 使用实体框架从集合中删除项目

    我正在使用DDD 我有一个 Product 类 它是一个聚合根 public class Product IAggregateRoot public virtual ICollection
  • ruby 正则表达式匹配模式的多次出现

    我正在寻找构建一个 ruby 正则表达式来匹配模式的多次出现并将它们返回到数组中 模式很简单 即 两个左括号 一个或多个字符 后跟两个右括号 这就是我所做的 str Some random text lead first name and
  • ios 在后台处理推送通知

    我想保存应用程序处于后台状态时到达的推送通知 我知道关于 void application UIApplication application didReceiveRemoteNotification NSDictionary userIn
  • 有没有办法将变量从 javascript 导入到 sass 或反之亦然?

    我正在制作一个依赖于块概念的 CSS 网格系统 所以我有一个基本文件 例如 max columns 4 block width 220px block height 150px block margin 10px 它被 mixin 使用 m
  • 在 C++11 中省略返回类型

    我最近发现自己在 C 11 模式下的 gcc 4 5 中使用了以下宏 define RETURN x gt decltype x return x 并编写这样的函数 template
  • 有什么工具可以说明每种方法运行需要多长时间?

    我的程序的某些部分速度很慢 我想知道是否有我可以使用的工具 例如它可以告诉我可以运行 methodA 花了 100ms 等等 或者类似的有用信息 如果您使用的是 Visual Studio Team System 性能工具 中有一个内置分析
  • 在 Perl 中使用数据引用的正确方法

    我有一组想要处理的数据 为了简化我的代码 最好通过指向原始数据的引用数组来访问我的数据的某些子集 比解释更好的是 我写下了这个例子 它还没有工作 最后 我想更新原始数据 而不必更新所有子集 用 Perl 可以做这样的事情吗 usr bin
  • Perl:HTTP::微小删除留下损坏的锚标记

    我编写了一个脚本 该脚本收集从数据库读取的缓冲区内的所有 URL 检查该页面是否仍然存在 并使用 HTTP Tiny 从缓冲区中删除 URL 如果该 URL 无法访问或返回无效 问题是 HTTP Tiny 删除左锚标记 例如此处无效的文本
  • IE 中的 HR 标签 - 删除边框

    在除 IE7 及更低版本之外的其他浏览器中 hr 在 hr 标签周围显示边框 但我不希望它出现 我已经尝试过这个解决方案 但它周围似乎仍然有边框 它看起来像这样 我该如何摆脱它 See http webdesign about com od
  • 使用 Guice 优化注册表

    你好 今天思考了一种优化 有一些疑问 语境 我正在使用 Guice 2 进行 Java 开发 在我的网络应用程序中 我有一个转换器注册表 可以即时转换为某种类型 转换器描述如下 public class StringToBoolean im
  • 判断一个数字是否能被 3 或 5 整除 (FizzBu​​zz)

    如何根据输出是否能被 3 或 5 整除来更改输出 如果它能被 3 整除 我想显示 rock 如果它能被 5 整除 我想显示 star 类似于 FizzBu zz 如果两者都有 他们都会看到 这是我的代码 if var n Math floo
  • 如何在react-三纤维中提取并播放动画

    嗯 我有 gltf 动画模型 我成功加载模型 但无法播放嵌入的动画 我想知道是否可以以任何方式解决它 顺便说一句 我正在反应中工作 先感谢您 在这里您可以找到型号https drive google com file d 1ZVyklaQu
  • 揭秘sharedctypes性能

    在 python 中 可以在多个进程之间共享 ctypes 对象 然而我注意到分配这些对象似乎非常昂贵 考虑以下代码 from multiprocessing import sharedctypes as sct import ctypes