8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

为什么“数组 % 1”对于较小的数字的运行速度比对于较大的数字的运行速度慢 150%?

Conrado 3月前

266 0

如果 numpy 足够聪明,能够看到数组 % 1 给出一个常量结果(对于该 dtype),并且其运行时与输入无关,那么我就可以理解。如果运行时

如果 numpy 足够聪明,能够看到 array % 1 给出一个恒定结果(对于 dtype ),并且其运行时间与输入无关,那么我就可以理解。我还可以理解,如果对于较大的数字,运行时间会更长,可能是因为除以一个大数字需要更长的时间(比较 为什么 Python 中的浮点除法对于较小的数字更快? )。但是为什么 array % 1 运行 更长

from timeit import timeit

import numpy as np

a1 = np.random.randint(500,        size=20_000_000, dtype=np.int32) - 250
a2 = np.random.randint(20_000_000, size=20_000_000, dtype=np.int32) - 250

print(timeit(lambda: a1 % 1, number=5))
print(timeit(lambda: a2 % 1, number=5))
print(timeit(lambda: a1 % 1, number=5))
print(timeit(lambda: a2 % 1, number=5))

输出:

0.4755298000000039
0.19294559999980265
0.460197700000208
0.19560679999995045

Numpy信息:

{'numpy_version': '2.0.0',
  'python': '3.12.4 (tags/v3.12.4:8e8a4ba, Jun  6 2024, 19:30:16) [MSC v.1940 '
            '64 bit (AMD64)]',
  'uname': uname_result(system='Windows', node='hostname', release='11', version='10.0.22631', machine='AMD64')},
 {'simd_extensions': {'baseline': ['SSE', 'SSE2', 'SSE3'],
                      'found': ['SSSE3',
                                'SSE41',
                                'POPCNT',
                                'SSE42',
                                'AVX',
                                'F16C',
                                'FMA3',
                                'AVX2',
                                'AVX512F',
                                'AVX512CD',
                                'AVX512_SKX',
                                'AVX512_CLX',
                                'AVX512_CNL',
                                'AVX512_ICL'],
                      'not_found': []}}]
帖子版权声明 1、本帖标题:为什么“数组 % 1”对于较小的数字的运行速度比对于较大的数字的运行速度慢 150%?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Conrado在本站《numpy》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 我无法使用 numpy 1.26.4 重现此问题。所有 4 个运行时间约为 0.42-0.45 秒。

  • 我已经使用 numpy 在 Python 中实现了 Sokoban 级别生成器,但当我运行代码时,它不会打印任何内容或生成预期的 Sokoban 级别。生成器应该经过

    我已经使用 numpy 在 Python 中实现了 Sokoban 级别生成器,但是当我运行代码时,它不会打印任何内容或生成预期的 Sokoban 级别。生成器应该经历几个步骤:

    1. 放置边界:用墙围绕关卡(#)
    2. 放置墙壁:在关卡内添加随机墙壁以增加复杂性。
    3. 放置玩家:将玩家(@)定位在随机位置。
    4. 放置盒子和目标:随机添加盒子 ($) 及其各自的目标 (.)。
    5. 检查可解性:确认所有箱子都能达到各自的目标,从而保证关卡可解。
    6. 生成和返回级别:构建并返回表示推箱子级别的二维列表。

    代码实现:

    import numpy as np
    
    class LevelGenerator:
        def __init__(self, size, num_walls):
            self.size = size
            self.num_walls = num_walls
            self.level = np.full((size, size), ' ')
            self.generate_level()
    
        def place_borders(self):
            self.level[0, :] = '#'
            self.level[:, 0] = '#'
            self.level[-1, :] = '#'
            self.level[:, -1] = '#'
    
        def place_walls(self):
            for _ in range(self.num_walls):
                while True:
                    x, y = np.random.randint(1, self.size-1, size=2)
                    if self.level[x, y] == ' ':
                        self.level[x, y] = '#'
                        break
    
        def place_player(self):
            while True:
                x, y = np.random.randint(1, self.size-1, size=2)
                if self.level[x, y] == ' ':
                    self.level[x, y] = '@'
                    self.player_pos = (x, y)
                    break
    
        def place_boxes_and_goals(self):
            num_boxes = (self.size - 2) // 2
            self.boxes = []
            self.goals = []
            for _ in range(num_boxes):
                while True:
                    x, y = np.random.randint(1, self.size-1, size=2)
                    if self.level[x, y] == ' ':
                        self.level[x, y] = '$'
                        self.boxes.append((x, y))
                        break
            for _ in range(num_boxes):
                while True:
                    x, y = np.random.randint(1, self.size-1, size=2)
                    if self.level[x, y] == ' ':
                        self.level[x, y] = '.'
                        self.goals.append((x, y))
                        break
    
        def is_solvable(self):
            return all(self.is_reachable(box) for box in self.boxes) and all(self.is_reachable(goal) for goal in self.goals)
    
        def is_reachable(self, target):
            x, y = target
            if self.level[x, y] in '#':
                return False
    
            visited = set()
            stack = [self.player_pos]
            
            while stack:
                cx, cy = stack.pop()
                if (cx, cy) == target:
                    return True
                if (cx, cy) in visited:
                    continue
                visited.add((cx, cy))
    
                for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
                    nx, ny = cx + dx, cy + dy
                    if 0 <= nx < self.size and 0 <= ny < self.size and self.level[nx, ny] not in '#$' and (nx, ny) not in visited:
                        stack.append((nx, ny))
    
            return False
    
        def generate_level(self):
            while True:
                self.level.fill(' ')
                self.place_borders()
                self.place_walls()
                self.place_player()
                self.place_boxes_and_goals()
                if self.is_solvable():
                    break
    
        def get_level(self):
            return self.level
    # Example usage
    print("Generating Sokoban level...")
    size = 10
    num_walls = 20
    generator = LevelGenerator(size, num_walls)
    level = generator.get_level()
    print("\n".join("".join(row) for row in level))
    

    运行代码后,程序似乎无限期地挂起,没有显示任何内容,而是打印出推箱子的等级。我期望程序根据指定的 (size = 10) 墙壁大小和数量 (num_walls = 20) .

    有人能帮忙找出是什么原因导致代码无法显示或打印生成的推箱子关卡吗?如果您能提供任何关于如何修复此问题的见解或建议,我们将不胜感激。

  • ADR 3月前 0 只看Ta
    引用 4

    我用 numpy 1.26.2 和 numpy 2.0.0 重现了它

  • 通常可以轻松调试“挂起”的东西。只需附加一个调试器并查看调用堆栈即可。

  • Rig 3月前 0 只看Ta
    引用 6

    我可以在 Linux 上使用 numpy 2.0.0 重现此问题,但不能在 macOS (Intel) 上重现。 (还没有在 Linux 上尝试过 numpy 1.26。)

  • 回溯(最近一次调用最后一次):文件 \'C:\Users\mohit\OneDrive\Desktop\Front End\Flask\pythonProject1\tut2.py\',第 8 行,位于导入 imutils 文件 \'C:\Users\mohit\

    Traceback (most recent call last):  File "C:\Users\mohit\OneDrive\Desktop\Front End\Flask\pythonProject1\tut2.py", line 8, in <module>
        import imutils
      File "C:\Users\mohit\OneDrive\Desktop\Front End\Flask\pythonProject1\.venv\Lib\site-packages\imutils\__init__.py", line 8, in <module>
        from .convenience import translate
      File "C:\Users\mohit\OneDrive\Desktop\Front End\Flask\pythonProject1\.venv\Lib\site-packages\imutils\convenience.py", line 6, in <module>
        import cv2
      File "C:\Users\mohit\OneDrive\Desktop\Front End\Flask\pythonProject1\.venv\Lib\site-packages\cv2\__init__.py", line 181, in <module>
        bootstrap()
      File "C:\Users\mohit\OneDrive\Desktop\Front End\Flask\pythonProject1\.venv\Lib\site-packages\cv2\__init__.py", line 153, in bootstrap
        native_module = importlib.import_module("cv2")
      File "C:\Program Files\Python312\Lib\importlib\__init__.py", line 90, in import_module
        return _bootstrap._gcd_import(name[level:], package, level)
    AttributeError: _ARRAY_API not found
    Traceback (most recent call last):
      File "C:\Users\mohit\OneDrive\Desktop\Front End\Flask\pythonProject1\tut2.py", line 8, in <module>
        import imutils
      File "C:\Users\mohit\OneDrive\Desktop\Front End\Flask\pythonProject1\.venv\Lib\site-packages\imutils\__init__.py", line 8, in <module>
        from .convenience import translate
      File "C:\Users\mohit\OneDrive\Desktop\Front End\Flask\pythonProject1\.venv\Lib\site-packages\imutils\convenience.py", line 6, in <module>
        import cv2
      File "C:\Users\mohit\OneDrive\Desktop\Front End\Flask\pythonProject1\.venv\Lib\site-packages\cv2\__init__.py", line 181, in <module>
        bootstrap()
      File "C:\Users\mohit\OneDrive\Desktop\Front End\Flask\pythonProject1\.venv\Lib\site-packages\cv2\__init__.py", line 153, in bootstrap
        native_module = importlib.import_module("cv2")
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "C:\Program Files\Python312\Lib\importlib\__init__.py", line 90, in import_module
        return _bootstrap._gcd_import(name[level:], package, level)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ImportError: numpy.core.multiarray failed to import
    

    使用 NumPy 1.x 编译的模块无法在 NumPy2.0.0 中运行,因为它可能会崩溃。要支持 NumPy 的 1.x 和 2.x 版本,模块必须使用 NumPy 2.0 编译。某些模块可能需要重建,例如使用“pybind11>=2.12”。

    如果您是该模块的用户,最简单的解决方案是降级到“numpy<2”或尝试升级受影响的模块。我们预计某些模块需要时间来支持 NumPy 2。

    我试图在 Pycharm 中制作一个使用 python 的 cv2 和 imutils 库的 flask 应用程序。但 Pycharm 中似乎存在问题。

    错误似乎出现在导入语句中:

    import imutils
    import cv2 
    

    我在网上看了关于如何下载 cv2 和 imutils 的教程,但下载之后似乎仍然存在问题。

    该错误与 Numpy 的升级版本有关,我不明白为什么会发生这种情况,因为我没有使用 Numpy。

    我仍然升级了 Numpy 的版本,但它不起作用。我尝试了人们建议的多种方法,但没有任何效果,我想我不确定如何制作 docker。

    该问题可能与 Pycharm 有关。我不确定。

  • 对于学习调试指导,有一些有用的阅读材料:

  • 我认为这可能是由分支预测引起的。例如,

  • \'我没有使用 Numpy\' —— OpenCV 的 Python 绑定确实使用了它。

  • 首先,我至少会放一个基本的“print”语句来看一下这里的行为:

  • 引用 12

    哦,我发现一个问题,这是因为级别生成器不断生成错误级别

  • 如何使用 numpy '编译'你的模块?

  • TL;DR: 这种影响是由 多种技术细节共同 。这不仅是由于 分支预测 问题(如评论中所述),还由于 Numpy 实现未 针对此用例进行优化, 并且 编译器优化较差 (尤其是 GCC/MSVC)。使用 Clang 可解决此问题。


    内部原理

    为了执行此操作,Numpy 使用 generic_wrapped_legacy_loop 的另一个函数 INT_remainder 。后者计算单个数组项的模数。它由一个相对复杂的通用代码(很难找到)生成,可 在此处 。对于您的情况,该函数基本上可以简化为以下代码:

    static void INT_remainder(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func)
    {
        BINARY_LOOP {
            const int in1 = *(int*)ip1; // An item of the input array
            const int in2 = *(int*)ip2; // The divisor which is set to 1
    
            // Overflow check
            if (NPY_UNLIKELY((in2 == 0) || (in1 == NPY_MIN_INT && in2 == -1))) {
                FLAG_IF_DIVIDEBYZERO(in2);
                *(int *)op1 = 0;
            }
            else
            {
                // handle mixed case the way Python does
                const int rem = in1 % in2;
                if ((in1 > 0) == (in2 > 0) || rem == 0) {
                    *(int*)op1 = rem;
                }
                else {
                    *(int*)op1 = rem + in2;
                }
            }
        }
    }
    

    第一个条件是溢出检查,在您的情况下它永远不会发生,并且由于它被定义为不太可能发生,因此它应该很便宜。在第二部分中, rem 在您的情况下应该始终为 0,因为 in2 为 1。因此, if 条件始终为真。但是,按照 C/C++ 标准中的定义,它是延迟执行的(从左到右)。这意味着 in1 > 0 首先 in2 > 0 评估和,然后评估相等性,最后 rem == 0 仅当第一个不为真时才评估。事情 in1 > 0 有时是真的,有时 The outcome of the in1 > 0 condition is dependent of the input array which is unpredictable 。由于 分支预测 。有关更多信息,请阅读 什么是分支预测? .

    第二个用例( a2 )速度更快,因为条件更经常为真,因此您的 CPU 可以预测该值可能为真,并推测性地采取由此产生的相关条件分支。与此同时,在第一种情况下,CPU 无法预测条件的结果,因此您需要付出不可预测的条件分支(例如管道停顿)的开销。

    在我的计算机上 分析 Numpy 的汇编代码 可以确认这一效果


    笔记和讨论

    分支预测在这里是个问题,这很令人遗憾,因为条件的结果最终总是正确的!如果条件是 rem == 0 || (in1 > 0) == (in2 > 0) ,那么在这种情况下就不会有任何性能问题。然而,问题出在模 2 的正随机整数上(这种情况更常见)……

    **虽然 (in1 > 0) == (in2 > 0) 应该是 无分支实现,但目标编译器 在这种情况下选择生成条件分支,从而导致观察到的效果。对于无分支汇编代码来说,这不是问题。无分支实现通常比预测良好的条件分支慢一点,并且(明显)比预测错误的条件分支快。因此,这似乎是个好主意。


    实验解决方案

    目标编译器是运行在 Linux 上的我的机器上的 GCC (10.2)。不幸的是,GCC 往往会生成这样的代码。软件包 pip (如 Numpy 的软件包)通常在 Linux 上使用 GCC 构建,在 Windows 上使用据我所知 MSVC 构建(生成的代码通常比 GCC 更差——尤其是在这里)。但是, 可以使用另一个编译器编译 Numpy 。事实证明,在这种情况下,Clang 似乎可以生成更好的代码(参见 GodBolt )。现在,您可以轻松地使用 Clang 构建 Numpy 2.0 并使用以下命令进行测试:

    # Packages Debian testing: sudo apt install virtualenv libpython3.13-dev build-essential pkg-config clang
    cd /tmp
    virtualenv venv
    ./venv/bin/pip install ipython meson ninja cython spin
    export PATH=$PWD/venv/bin:$PATH
    git clone https://github.com/numpy/numpy.git
    cd numpy
    git checkout v2.0.0
    git submodule update --init
    export CC=/usr/bin/clang
    export CXX=/usr/bin/clang++
    spin ipython
    

    以下是我的机器(i5-9600KF CPU)上的结果:

    GCC 13.2.0 (i.e. with conditional branches):
        0.7204837069984933
        0.2300826290011173
        0.7204179290001775
        0.23008121000020765
    
    Clang 16.0.6 (i.e. without conditional branches):
        0.31062674399800017
        0.3103496809999342
        0.31044369300070684
        0.3100759939989075
    

    正如预期的那样, Clang 生成的无分支 汇编代码在预测错误时比使用 GCC 条件分支的版本更快,但在预测错误时则不然。 它没有观察到性能问题 .

    请注意,Clang 是 MacOS 上的默认编译器,因此在 MacOS 上运行的用户不会注意到这个问题。

    请注意,这两种实现对于整数来说都不是最优的,因为它们无法从 SIMD 指令中获益(尽管这并不容易,因为主流 CPU 上没有 div / idiv 指令)。更不用说在这个非常具体(不太可能)的用例中,人们可以将最终数组设置为 0。

  • 哇。没什么可说的了。

  • 我正在尝试学习 pandas、numpy 和 matplotlib,但是当我导入它们并运行代码时,vscode 找不到模块。在此处输入图像描述我尝试检查是否已使用 c 下载了它......

    我正在尝试学习 pandas、numpy 和 matplotlib,但当我导入它们并运行代码时,vscode 找不到模块。 在此处输入图片描述

    我尝试使用 cmd 检查是否已下载,然后它们出现了。 在此处输入图片描述 有人能帮忙吗?谢谢,非常感谢!

  • @PrathikKini 不要破坏这样的问题。你删除了大部分的回溯,并添加了不必要的双重强调格式。将代码包装在代码围栏中使得帖子具有足够的可读性。

  • 因此,对于这种情况,有很多答案,但据我所知,应该是,

    • CPU 缓存或分支预测
    • 数据如何分布
    • 矢量化区域
    • 或者主要问题是潜在的类型转换

    您能做的最好的事情是使用外部工具来分析和检查这一点。有一个可以与 python 一起使用的工具,称为 cProfile,我很少使用它,但为了实现它非常容易。

    您可以使用下面的代码来分析这一点。

    def profile_code():
    a1 % 1
    a2 % 1
    
    cProfile.run('profile_code()', 'output.pstats')
    p = pstats.Stats('output.pstats')
    p.sort_stats('cumulative').print_stats(10)
    
  • \'输入图片描述\' 是一个占位符,其中

  • 如果您能展示结果来说明问题,这可能是个不错的答案。在评论中,@Dogbert 证明了分支预测的数量不同。您的技术是否显示了类似的结果?

返回
作者最近主题: