NumPy 1.26 中文文档(四十九)

2024-07-01 14:57:50 浏览数 (1)

原文:numpy.org/doc/

C API 弃用

原文:numpy.org/doc/1.26/reference/c-api/deprecations.html

背景

多年来,NumPy 为第三方扩展暴露的 API 已发展壮大,并使程序员能够直接从 C 中访问 NumPy 功能。这个 API 最好被描述为“有机的”。它是由多种竞争性的愿望和多种观点多年形成的,受到希望使用户能够从 Numeric 和 Numarray 迁移到 NumPy 方面的强烈影响。核心 API 始于 1995 年的 Numeric,并有一些模式,比如大量使用宏来模仿 Python 的 C-API 以及适应 90 年代晚期的编译器技术。只有一小群志愿者很少有时间投入到改进这个 API 上。

目前正在努力改进 API。在这个努力中,重要的是要确保适用于 NumPy 1.X 的代码继续适用于 NumPy 1.X。同时,某些 API 将被标记为弃用,以便能朝着未来的代码避开这些 API,并采用更好的做法。

C API 中弃用标记的另一个重要作用是朝着隐藏 NumPy 实现的内部细节前进。对于那些需要直接、轻松地访问 ndarrays 数据的人来说,这不会移除这种功能。相反,有许多潜在的性能优化需要改变实现细节,并且由于保留 ABI 兼容性的价值很高,NumPy 开发人员现在无法尝试这些优化。通过弃用这种直接访问方法,将来我们将能够以我们目前无法做到的方式提高 NumPy 的性能。

弃用机制 NPY_NO_DEPRECATED_API

在 C 语言中,没有像 Python 那样支持弃用警告需要进行改进的功能。处理弃用的一种方法是在文档和发布说明中标记它们,然后在将来的主要版本(如 NumPy 2.0 及以后)中删除或更改弃用的功能。但 NumPy 的次要版本不应有导致在之前的次要版本上正常运行的代码无法运行的主要 C-API 更改。例如,我们将尽力确保在 NumPy 1.4 上编译和运行的代码应该继续在 NumPy 1.7 上运行(但可能会有编译器警告)。

要使用 NPY_NO_DEPRECATED_API 机制,您需要在#include 任何 NumPy 头文件之前将其#define 为 NumPy 的目标 API 版本。如果您希望确认您的代码对 1.7 干净,在 C 中使用:

代码语言:javascript复制
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION 

在支持#warning 机制的编译器上,如果您没有定义符号 NPY_NO_DEPRECATED_API,NumPy 将发出一个编译器警告。这样一来,第三方开发人员可能没有仔细阅读发布说明的事实会被标记为有弃用功能。

请注意,定义 NPY_NO_DEPRECATED_API 并不足以使您的扩展 ABI 与给定的 NumPy 版本兼容。请参阅对下游包作者。

背景

NumPy 为第三方扩展所公开的 API 已经经过多年的版本发布,并允许程序员直接从 C 访问 NumPy 功能。这个 API 最好可以描述为“有机的”。多年来,它已经从多个竞争的愿望和多个观点中出现,并且受到了从 Numeric 和 Numarray 转移到 NumPy 的用户方便的强烈影响。核心 API 最初是由 1995 年的 Numeric 创建的,存在一些模式,例如大量使用的宏,用于模仿 Python 的 C-API,并考虑了 90 年代后期的编译器技术。并且,有一个只有很少时间来改进这个 API 的小团队志愿者。

正在努力改进 API。在这个努力中,确保为 NumPy 1.X 编写的代码仍然可以为 NumPy 1.X 编译非常重要。同时,某些 API 将被标记为弃用,以便未来的代码可以避免使用这些 API,并遵循更好的实践。

C API 中弃用标记扮演的另一个重要角色是朝着隐藏 NumPy 实现的内部细节。对于那些需要直接、简单地访问 ndarrays 数据的人来说,这并不会删除这种能力。相反,有许多潜在的性能优化需要更改实现细节,而目前由于保存 ABI 兼容性的重要性,NumPy 开发人员无法尝试这些优化措施。通过弃用这种直接访问方式,我们将来能够以目前无法实现的方式改进 NumPy 的性能。

弃用机制 NPY_NO_DEPRECATED_API

在 C 中,没有相当于 Python 支持的弃用警告的机制。进行弃用的一种方法是在文档和发布说明中标记它们,然后在将来的主要版本(NumPy 2.0 及以后)中删除或更改已弃用的功能。NumPy 的次要版本不应该有主要的 C-API 更改,这会阻止之前的次要版本上运行的代码。例如,我们将尽力确保在 NumPy 1.4 上编译并运行的代码应该在 NumPy 1.7 上(可能会出现编译器警告)继续运行。

要使用 NPY_NO_DEPRECATED_API 机制,在#include 任何 NumPy 头文件之前,您需要将其定义为 NumPy 的目标 API 版本。如果您想确认您的代码是否适用于 1.7,请使用:

代码语言:javascript复制
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION 

在支持#warning 机制的编译器上,如果您没有定义符号 NPY_NO_DEPRECATED_API,NumPy 会发出编译器警告。这样,那些可能没有仔细阅读发布说明的第三方开发人员将会注意到已经弃用的事实。

请注意,仅定义 NPY_NO_DEPRECATED_API 并不足以使您的扩展与特定的 NumPy 版本 ABI 兼容。参见面向下游软件包作者。

NumPy 中的内存管理

原文:numpy.org/doc/1.26/reference/c-api/data_memory.html

numpy.ndarray 是一个 Python 类。它需要额外的内存分配来保存 numpy.ndarray.stridesnumpy.ndarray.shapenumpy.ndarray.data 属性。这些属性在创建 Python 对象后在 new 中特别分配。stridesshape 存储在内部分配的内存块中。

用于存储实际数组值的 data 分配(在object数组的情况下可能是指针)可能非常大,因此 NumPy 提供了管理其分配和释放的接口。本文详细介绍了这些接口的工作原理。

历史概述

自版本 1.7.0 起,NumPy 暴露了一组 PyDataMem_* 函数(PyDataMem_NEWPyDataMem_FREEPyDataMem_RENEW),分别由 allocfreerealloc 支持。在该版本中,NumPy 也公开了下面描述的 PyDataMem_EventHook 函数(现已废弃),它封装了 OS 级别的调用。

自那些早期以来,Python 也改进了其内存管理能力,并在 3.4 版本中开始提供各种管理策略。这些例程分为一组域,每个域都有一个用于内存管理的 PyMemAllocatorEx 结构。Python 还添加了一个用于跟踪对各种例程的调用的 tracemalloc 模块。这些跟踪钩子已添加到 NumPy 的 PyDataMem_* 例程中。

NumPy 在其内部的 npy_alloc_cachenpy_alloc_cache_zeronpy_free_cache 函数中添加了一小块已分配内存的缓存。这些函数分别封装了 allocalloc-and-memset(0)free,但当调用 npy_free_cache 时,它会将指针添加到一个以大小标记的可用块的短列表中。这些块可以被后续对 npy_alloc* 的调用重新使用,避免内存抖动。

NumPy 中的可配置内存例程(NEP 49)

用户可能希望使用自己的内部数据内存例程来覆盖内部的数据内存例程。由于 NumPy 不使用 Python 领域策略来管理数据内存,它提供了一组替代的 C-API 来更改内存例程。对于大块对象数据,没有 Python 领域范围的策略,因此这些不太适合 NumPy 的需求。希望更改 NumPy 数据内存管理例程的用户可以使用 PyDataMem_SetHandler,它使用一个 PyDataMem_Handler 结构体来保存用于管理数据内存的函数指针。调用仍然由内部例程包装以调用 PyTraceMalloc_TrackPyTraceMalloc_Untrack,并将使用已弃用的 PyDataMem_EventHookFunc 机制。由于函数可能在进程的生命周期内发生变化,每个 ndarray 都携带着在其实例化时使用的函数,并且这些函数将用于重新分配或释放该实例的数据内存。

代码语言:javascript复制
type PyDataMem_Handler

一个用于保存用于操作内存的函数指针的结构体

代码语言:javascript复制
typedef  struct  {
  char  name[127];  /* multiple of 64 to keep the struct aligned */
  uint8_t  version;  /* currently 1 */
  PyDataMemAllocator  allocator;
}  PyDataMem_Handler; 

分配器结构体所在位置

代码语言:javascript复制
/* The declaration of free differs from PyMemAllocatorEx */
typedef  struct  {
  void  *ctx;
  void*  (*malloc)  (void  *ctx,  size_t  size);
  void*  (*calloc)  (void  *ctx,  size_t  nelem,  size_t  elsize);
  void*  (*realloc)  (void  *ctx,  void  *ptr,  size_t  new_size);
  void  (*free)  (void  *ctx,  void  *ptr,  size_t  size);
}  PyDataMemAllocator; 
代码语言:javascript复制
*PyDataMem_SetHandler( *handler)

设置新的分配策略。如果输入值为NULL,则将策略重置为默认值。返回先前的策略,如果发生错误则返回NULL。我们包装用户提供的函数,以便它们依然调用 Python 和 numpy 内存管理回调钩子。

代码语言:javascript复制
*PyDataMem_GetHandler()

返回将用于为下一个 PyArrayObject 分配数据的当前策略。失败时返回NULL

关于设置和使用 PyDataMem_Handler 的示例,请参见 numpy/core/tests/test_mem_policy.py 中的测试

代码语言:javascript复制
void PyDataMem_EventHookFunc(void *inp, void *outp, size_t size, void *user_data);

在数据内存操作期间将调用此函数

代码语言:javascript复制
*PyDataMem_SetEventHook( *newhook, void *user_data, void **old_data)

为 numpy 数组数据设置分配事件钩子。

返回指向先前钩子的指针或NULL。如果 old_data 非NULL,则将将先前的 user_data 指针复制到其中。

如果非NULL,钩子将在每个 PyDataMem_NEW/FREE/RENEW 结束时被调用

代码语言:javascript复制
result  =  PyDataMem_NEW(size)  ->  (*hook)(NULL,  result,  size,  user_data)
PyDataMem_FREE(ptr)  ->  (*hook)(ptr,  NULL,  0,  user_data)
result  =  PyDataMem_RENEW(ptr,  size)  ->  (*hook)(ptr,  result,  size,  user_data) 

当调用钩子时,全局解释器锁将由调用线程持有。如果执行可能导致新分配事件的操作(例如创建/销毁 numpy 对象,或创建/销毁可能导致垃圾回收的 Python 对象),则钩子应该被编写为可重入。

在 v1.23 中弃用

如果没有设置策略,释放时会发生什么

一种罕见但有用的技术是在 NumPy 之外分配一个缓冲区,使用PyArray_NewFromDescr将缓冲区包装在一个ndarray中,然后将OWNDATA标志切换为 true。当释放ndarray时,应调用ndarrayPyDataMem_Handler中的适当函数来释放缓冲区。但是PyDataMem_Handler字段从未设置过,它将是NULL。出于向后兼容性的原因,NumPy 将调用free()来释放缓冲区。如果将NUMPY_WARN_IF_NO_MEM_POLICY设置为1,将发出警告。当前的默认设置是不发出警告,但在将来的 NumPy 版本可能会更改。

一个更好的技术是将 PyCapsule 用作基本对象:

代码语言:javascript复制
/* define a PyCapsule_Destructor, using the correct deallocator for buff */
void  free_wrap(void  *capsule){
  void  *  obj  =  PyCapsule_GetPointer(capsule,  PyCapsule_GetName(capsule));
  free(obj);
};

/* then inside the function that creates arr from buff */
...
arr  =  PyArray_NewFromDescr(...  buf,  ...);
if  (arr  ==  NULL)  {
  return  NULL;
}
capsule  =  PyCapsule_New(buf,  "my_wrapped_buffer",
  (PyCapsule_Destructor)&free_wrap);
if  (PyArray_SetBaseObject(arr,  capsule)  ==  -1)  {
  Py_DECREF(arr);
  return  NULL;
}
... 

使用 np.lib.tracemalloc_domain 进行内存跟踪的示例

注意自 Python 3.6(或更新版本)以来,内置的 tracemalloc 模块可以用于跟踪 NumPy 内部的分配。NumPy 将其 CPU 内存分配放入 np.lib.tracemalloc_domain 域中。有关更多信息,请参阅:https://docs.python.org/3/library/tracemalloc.html

这是一个使用 np.lib.tracemalloc_domain 的示例:

代码语言:javascript复制
"""
 The goal of this example is to show how to trace memory
 from an application that has NumPy and non-NumPy sections.
 We only select the sections using NumPy related calls.
"""

import tracemalloc
import numpy as np

# Flag to determine if we select NumPy domain
use_np_domain = True

nx = 300
ny = 500

# Start to trace memory
tracemalloc.start()

# Section 1
# ---------

# NumPy related call
a = np.zeros((nx,ny))

# non-NumPy related call
b = [i**2 for i in range(nx*ny)]

snapshot1 = tracemalloc.take_snapshot()
# We filter the snapshot to only select NumPy related calls
np_domain = np.lib.tracemalloc_domain
dom_filter = tracemalloc.DomainFilter(inclusive=use_np_domain,
                                      domain=np_domain)
snapshot1 = snapshot1.filter_traces([dom_filter])
top_stats1 = snapshot1.statistics('traceback')

print("================ SNAPSHOT 1 =================")
for stat in top_stats1:
    print(f"{stat.count} memory blocks: {stat.size  /  1024:.1f} KiB")
    print(stat.traceback.format()[-1])

# Clear traces of memory blocks allocated by Python
# before moving to the next section.
tracemalloc.clear_traces()

# Section 2
#----------

# We are only using NumPy
c = np.sum(a*a)

snapshot2 = tracemalloc.take_snapshot()
top_stats2 = snapshot2.statistics('traceback')

print()
print("================ SNAPSHOT 2 =================")
for stat in top_stats2:
    print(f"{stat.count} memory blocks: {stat.size  /  1024:.1f} KiB")
    print(stat.traceback.format()[-1])

tracemalloc.stop()

print()
print("============================================")
print("nTracing Status : ", tracemalloc.is_tracing())

try:
    print("nTrying to Take Snapshot After Tracing is Stopped.")
    snap = tracemalloc.take_snapshot()
except Exception as e:
    print("Exception : ", e) 

历史概览

从版本 1.7.0 开始,NumPy 公开了一组 PyDataMem_* 函数(PyDataMem_NEWPyDataMem_FREEPyDataMem_RENEW),它们分别由 allocfreerealloc 支持。在这个版本中,NumPy 还公开了 PyDataMem_EventHook 功能(现在已弃用),描述如下,它封装了 OS 级别的调用。

自那时起,Python 也提高了其内存管理能力,并从 3.4 版本开始提供了各种管理策略。这些例程被分为一组域,每个域都有一组用于内存管理的 PyMemAllocatorEx 结构的例程。Python 还添加了一个 tracemalloc 模块来跟踪各种例程的调用。这些跟踪钩子被添加到 NumPy PyDataMem_* 例程中。

NumPy 在其内部的 npy_alloc_cachenpy_alloc_cache_zeronpy_free_cache 函数中增加了一小块分配的内存缓存。这些函数分别封装了allocalloc-and-memset(0)free,但是当调用npy_free_cache时,它会将指针添加到一个短的可用块列表中并标记大小。这些块可以被后续对 npy_alloc* 的调用重新使用,避免内存抖动。

NumPy 中的可配置内存例程 (NEP 49)

用户可能希望用自己的内部数据内存例程覆盖内部数据内存例程。由于 NumPy 不使用 Python 域策略来管理数据内存,它提供了一组替代的 C-API 来更改内存例程。对于大块对象数据,Python 领域没有全局策略,因此这些策略不太适合 NumPy 的需要。希望更改 NumPy 数据内存管理例程的用户可以使用PyDataMem_SetHandler,它使用一个PyDataMem_Handler结构来保存用于管理数据内存的函数指针。调用仍然由内部例程包装以调用PyTraceMalloc_TrackPyTraceMalloc_Untrack,并将使用已弃用的PyDataMem_EventHookFunc机制。由于函数可能在进程的生命周期中发生变化,因此每个ndarray都携带了其实例化时使用的函数,并且这些函数将用于重新分配或释放实例的数据内存。

代码语言:javascript复制
type PyDataMem_Handler

用于保存用于操作内存的函数指针的结构

代码语言:javascript复制
typedef  struct  {
  char  name[127];  /* multiple of 64 to keep the struct aligned */
  uint8_t  version;  /* currently 1 */
  PyDataMemAllocator  allocator;
}  PyDataMem_Handler; 

分配器结构所在位置

代码语言:javascript复制
/* The declaration of free differs from PyMemAllocatorEx */
typedef  struct  {
  void  *ctx;
  void*  (*malloc)  (void  *ctx,  size_t  size);
  void*  (*calloc)  (void  *ctx,  size_t  nelem,  size_t  elsize);
  void*  (*realloc)  (void  *ctx,  void  *ptr,  size_t  new_size);
  void  (*free)  (void  *ctx,  void  *ptr,  size_t  size);
}  PyDataMemAllocator; 
代码语言:javascript复制
*PyDataMem_SetHandler( *handler)

设置新的分配策略。如果输入值为NULL,将重置策略为默认值。返回上一个策略,如果发生错误则返回NULL。我们包装了用户提供的函数,以便它们仍然调用 Python 和 numpy 内存管理回调挂钩。

代码语言:javascript复制
*PyDataMem_GetHandler()

返回将用于为下一个PyArrayObject分配数据的当前策略。失败时,返回NULL

有关设置和使用 PyDataMem_Handler 的示例,请参见numpy/core/tests/test_mem_policy.py中的测试

代码语言:javascript复制
void PyDataMem_EventHookFunc(void *inp, void *outp, size_t size, void *user_data);

此函数将在数据内存操作期间调用

代码语言:javascript复制
*PyDataMem_SetEventHook( *newhook, void *user_data, void **old_data)

设置 numpy 数组数据的分配事件挂钩。

返回指向上一个挂钩的指针,或NULL。如果old_dataNULL,则将将前一个user_data指针复制到其中。

如果非NULL,将在每个PyDataMem_NEW/FREE/RENEW结束时调用挂钩:

代码语言:javascript复制
result  =  PyDataMem_NEW(size)  ->  (*hook)(NULL,  result,  size,  user_data)
PyDataMem_FREE(ptr)  ->  (*hook)(ptr,  NULL,  0,  user_data)
result  =  PyDataMem_RENEW(ptr,  size)  ->  (*hook)(ptr,  result,  size,  user_data) 

调用挂钩时,由调用线程持有 GIL。如果挂钩执行可能导致新的分配事件(如创建/销毁 numpy 对象,或创建/销毁可能导致 gc 的 Python 对象)的操作,应将挂钩编写为可重入。

已弃用于 v1.23

如果没有设置策略,则在释放内存时会发生什么

一种罕见但有用的技术是在 NumPy 之外分配一个缓冲区,使用PyArray_NewFromDescr将缓冲区包装在ndarray中,然后将OWNDATA标志切换为 true。当释放ndarray时,应调用ndarrayPyDataMem_Handler中的适当函数来释放缓冲区。但PyDataMem_Handler字段从未被设置过,它将为NULL。为了向后兼容,NumPy 将调用free()来释放缓冲区。如果将NUMPY_WARN_IF_NO_MEM_POLICY设置为1,则会发出警告。目前的默认设置是不发出警告,这在将来的 NumPy 版本中可能会改变。

更好的技术是使用PyCapsule作为基本对象。

代码语言:javascript复制
/* define a PyCapsule_Destructor, using the correct deallocator for buff */
void  free_wrap(void  *capsule){
  void  *  obj  =  PyCapsule_GetPointer(capsule,  PyCapsule_GetName(capsule));
  free(obj);
};

/* then inside the function that creates arr from buff */
...
arr  =  PyArray_NewFromDescr(...  buf,  ...);
if  (arr  ==  NULL)  {
  return  NULL;
}
capsule  =  PyCapsule_New(buf,  "my_wrapped_buffer",
  (PyCapsule_Destructor)&free_wrap);
if  (PyArray_SetBaseObject(arr,  capsule)  ==  -1)  {
  Py_DECREF(arr);
  return  NULL;
}
... 

使用np.lib.tracemalloc_domain进行内存跟踪的示例。

请注意,自 Python 3.6(或更新版本)以来,内置的tracemalloc模块可以用于跟踪 NumPy 内的分配。NumPy 将其 CPU 内存分配放入np.lib.tracemalloc_domain域中。有关附加信息,请查看:https://docs.python.org/3/library/tracemalloc.html

这是使用np.lib.tracemalloc_domain的示例。

代码语言:javascript复制
"""
 The goal of this example is to show how to trace memory
 from an application that has NumPy and non-NumPy sections.
 We only select the sections using NumPy related calls.
"""

import tracemalloc
import numpy as np

# Flag to determine if we select NumPy domain
use_np_domain = True

nx = 300
ny = 500

# Start to trace memory
tracemalloc.start()

# Section 1
# ---------

# NumPy related call
a = np.zeros((nx,ny))

# non-NumPy related call
b = [i**2 for i in range(nx*ny)]

snapshot1 = tracemalloc.take_snapshot()
# We filter the snapshot to only select NumPy related calls
np_domain = np.lib.tracemalloc_domain
dom_filter = tracemalloc.DomainFilter(inclusive=use_np_domain,
                                      domain=np_domain)
snapshot1 = snapshot1.filter_traces([dom_filter])
top_stats1 = snapshot1.statistics('traceback')

print("================ SNAPSHOT 1 =================")
for stat in top_stats1:
    print(f"{stat.count} memory blocks: {stat.size  /  1024:.1f} KiB")
    print(stat.traceback.format()[-1])

# Clear traces of memory blocks allocated by Python
# before moving to the next section.
tracemalloc.clear_traces()

# Section 2
#----------

# We are only using NumPy
c = np.sum(a*a)

snapshot2 = tracemalloc.take_snapshot()
top_stats2 = snapshot2.statistics('traceback')

print()
print("================ SNAPSHOT 2 =================")
for stat in top_stats2:
    print(f"{stat.count} memory blocks: {stat.size  /  1024:.1f} KiB")
    print(stat.traceback.format()[-1])

tracemalloc.stop()

print()
print("============================================")
print("nTracing Status : ", tracemalloc.is_tracing())

try:
    print("nTrying to Take Snapshot After Tracing is Stopped.")
    snap = tracemalloc.take_snapshot()
except Exception as e:
    print("Exception : ", e) 

CPU/SIMD 优化

原文:numpy.org/doc/1.26/reference/simd/index.html

NumPy 具有灵活的工作机制,允许它利用 CPU 拥有的 SIMD 特性,在所有流行的平台上提供更快和更稳定的性能。目前,NumPy 支持 X86、IBM/Power、ARM7 和 ARM8 架构。

NumPy 中的优化过程是在三个层次上进行的:

  • 代码使用通用的内部函数来编写,这是一组类型、宏和函数,通过使用保护,将它们映射到每个支持的指令集上,只有编译器识别他们时才可以使用。这使我们能够为相同功能生成多个内核,其中每个生成的内核表示一个或多个特定 CPU 特性的指令集。第一个内核表示最小(基线)CPU 特性,而其他内核则表示附加的(分派的)CPU 特性。
  • 编译时,使用 CPU 构建选项来定义要支持的最低和附加特性,基于用户选择和编译器支持。适当的内部函数与平台/架构内部函数叠加,并编译多个内核。
  • 运行时导入时,对 CPU 进行探测以获得支持的 CPU 特性集。使用机制来获取指向最适合的内核的指针,并且这将是调用函数的内核。

注意

NumPy 社区在实施此项工作之前进行了深入讨论,请查看NEP-38以获得更多澄清。

  • CPU 构建选项
    • 描述
    • 快速入门
      • 我正在为本地使用构建 NumPy
      • 我不想支持旧的x86架构处理器
      • 我遇到了与上述情况相同的情况,但使用了ppc64架构
      • AVX512 特性有问题吗?
    • 支持的特性
      • 在 x86 上
      • 在 IBM/POWER 大端
      • 在 IBM/POWER 小端
      • 在 ARMv7/A32
      • 在 ARMv8/A64
      • 在 IBM/ZSYSTEM(S390X)
    • 特殊选项
    • 行为
    • 平台差异
      • 在 x86::Intel 编译器
      • 在 x86::Microsoft Visual C/C
    • 构建报告
    • 运行时调度
  • CPU 调度器是如何工作的?
    • 1- Configuration
    • 2- 发现环境
    • 3- 验证请求的优化
    • 4- 生成主配置头文件
    • 5- Dispatch-able sources and configuration statements

CPU 构建选项

原文:numpy.org/doc/1.26/reference/simd/build-options.html

描述

以下选项主要用于更改针对特定 CPU 功能进行优化的默认行为:

  • --cpu-baseline:所需 CPU 功能的最小集合。 默认值为 min,提供可以安全运行在处理器系列内广泛平台上的最小 CPU 功能。 注意 在运行时,如果目标 CPU 不支持指定的任何功能,则 NumPy 模块将无法加载(引发 Python 运行时错误)。
  • --cpu-dispatch:分派的一组额外 CPU 功能。 默认值为 max -xop -fma4,启用所有 CPU 功能,除了 AMD 遗留功能(在 X86 的情况下)。 注意 在运行时,如果目标 CPU 不支持任何指定功能,则 NumPy 模块将跳过这些功能。

这些选项可以通过 distutils 命令 distutils.command.builddistutils.command.build_clibdistutils.command.build_ext 访问。它们接受一组 CPU 功能或收集几个功能的功能组,或者特殊选项执行一系列过程。

注意

如果用户未指定 build_clibbuild_ext,则将使用 build 的参数,其中也包含默认值。

自定义 build_extbuild_clib

代码语言:javascript复制
cd /path/to/numpy
python setup.py build --cpu-baseline="avx2 fma3" install --user 

仅自定义 build_ext

代码语言:javascript复制
cd /path/to/numpy
python setup.py build_ext --cpu-baseline="avx2 fma3" install --user 

仅自定义 build_clib

代码语言:javascript复制
cd /path/to/numpy
python setup.py build_clib --cpu-baseline="avx2 fma3" install --user 

您还可以通过 PIP 命令自定义 CPU/构建选项:

代码语言:javascript复制
pip install --no-use-pep517 --global-option=build 
--global-option="--cpu-baseline=avx2 fma3" 
--global-option="--cpu-dispatch=max" ./ 

快速开始

通常,默认设置不会强加可能在一些旧处理器上不可用的特定 CPU 功能。提高基线功能的上限通常会提高性能,也可能减小二进制文件大小。

下面是可能需要更改默认设置的最常见情况:

我正在为本地使用构建 NumPy

我不打算将构建结果导出给其他用户,也不打算针对与主机不同的 CPU 进行优化。

native 设置为基线,或者在您的平台不支持 native 选项的情况下手动指定 CPU 功能:

代码语言:javascript复制
python setup.py build --cpu-baseline="native" bdist 

对于这种情况,使用额外的 CPU 功能构建 NumPy 并不是必要的,因为所有支持的功能已经在基线功能中定义:

代码语言:javascript复制
python setup.py build --cpu-baseline=native --cpu-dispatch=none bdist 

注意

如果主机平台不支持 native,将引发致命错误。

我不想支持 x86 架构的旧处理器

由于大多数 CPU 现在至少支持 AVXF16C 功能,您可以使用:

代码语言:javascript复制
python setup.py build --cpu-baseline="avx f16c" bdist 

注意

--cpu-baseline 强制组合所有暗示功能,因此无需添加 SSE 功能。

我遇到了与上述情况相同的问题,但是针对 ppc64 架构

那么将基线功能的上限提升到 Power8:

代码语言:javascript复制
python setup.py build --cpu-baseline="vsx2" bdist 
遇到AVX512功能问题了吗?

你可能对包含AVX512或任何其他 CPU 功能有一些保留,想要排除已调度功能:

代码语言:javascript复制
python setup.py build --cpu-dispatch="max -avx512f -avx512cd 
-avx512_knl -avx512_knm -avx512_skx -avx512_clx -avx512_cnl -avx512_icl" 
bdist 

支持的功能

功能的名称可以表示一个功能或一组功能,如下表所示,支持的功能取决于最低兴趣:

注意

以下功能可能不被所有编译器支持,而且一些编译器在涉及AVX512AVX2FMA3等功能时可能会产生不同的暗示功能集。查看平台差异获取更多详细信息。

在 x86 上

名称

暗示

收集

SSE

SSE2

SSE2

SSE

SSE3

SSE SSE2

SSSE3

SSE SSE2 SSE3

SSE41

SSE SSE2 SSE3 SSSE3

POPCNT

SSE SSE2 SSE3 SSSE3 SSE41

SSE42

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT

AVX

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42

XOP

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX

FMA4

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX

F16C

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX

FMA3

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C

AVX2

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C

AVX512F

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2

AVX512CD

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F

AVX512_KNL

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD

AVX512ER AVX512PF

AVX512_KNM

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_KNL

AVX5124FMAPS AVX5124VNNIW AVX512VPOPCNTDQ

AVX512_SKX

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD

AVX512VL AVX512BW AVX512DQ

AVX512_CLX

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX

AVX512VNNI

AVX512_CNL

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX

AVX512IFMA AVX512VBMI

AVX512_ICL

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX AVX512_CLX AVX512_CNL

AVX512VBMI2 AVX512BITALG AVX512VPOPCNTDQ

AVX512_SPR

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX AVX512_CLX AVX512_CNL AVX512_ICL

AVX512FP16

在 IBM/POWER 大端

名称

意味着

VSX

VSX2

VSX

VSX3

VSX VSX2

VSX4

VSX VSX2 VSX3

在 IBM/POWER 小端

名称

意味着

VSX

VSX2

VSX2

VSX

VSX3

VSX VSX2

VSX4

VSX VSX2 VSX3

在 ARMv7/A32

名称

意味着

NEON

NEON_FP16

NEON

NEON_VFPV4

NEON NEON_FP16

ASIMD

NEON NEON_FP16 NEON_VFPV4

ASIMDHP

NEON NEON_FP16 NEON_VFPV4 ASIMD

ASIMDDP

NEON NEON_FP16 NEON_VFPV4 ASIMD

ASIMDFHM

NEON NEON_FP16 NEON_VFPV4 ASIMD ASIMDHP

在 ARMv8/A64

名称

意味着

NEON

NEON_FP16 NEON_VFPV4 ASIMD

NEON_FP16

NEON NEON_VFPV4 ASIMD

NEON_VFPV4

NEON NEON_FP16 ASIMD

ASIMD

NEON NEON_FP16 NEON_VFPV4

ASIMDHP

NEON NEON_FP16 NEON_VFPV4 ASIMD

ASIMDDP

NEON NEON_FP16 NEON_VFPV4 ASIMD

ASIMDFHM

NEON NEON_FP16 NEON_VFPV4 ASIMD ASIMDHP

在 IBM/ZSYSTEM(S390X)

名称

意味着

VX

VXE

VX

| VXE2 | VX VXE | ## 特殊选项

  • NONE: 不启用任何功能。
  • NATIVE: 启用主机 CPU 支持的所有 CPU 功能,此操作基于编译器标志(-march=native-xHost/QxHost
  • MIN: 启用可以安全运行在广泛平台上的最低 CPU 功能: 对于 Arch意味着x86(32 位模式)SSE SSE2x86_64SSE SSE2 SSE3IBM/POWER(大端模式)NONEIBM/POWER(小端模式)VSX VSX2ARMHFNONEARM64 A.K. AARCH64NEON NEON_FP16 NEON_VFPV4 ASIMDIBM/ZSYSTEM(S390X)NONE
  • MAX: 启用编译器和平台支持的所有 CPU 功能。
  • Operators-/ : 移除或添加功能,与选项MAXMINNATIVE一起使用。

行为

CPU 功能和其他选项不区分大小写,例如:

代码语言:javascript复制
python setup.py build --cpu-dispatch="SSE41 avx2 FMA3" 

请求的优化顺序不重要:

代码语言:javascript复制
python setup.py build --cpu-dispatch="SSE41 AVX2 FMA3"
# equivalent to
python setup.py build --cpu-dispatch="FMA3 AVX2 SSE41" 

逗号、空格或‘ ’都可以用作分隔符,例如:

代码语言:javascript复制
python setup.py build --cpu-dispatch="avx2 avx512f"
# or
python setup.py build --cpu-dispatch=avx2,avx512f
# or
python setup.py build --cpu-dispatch="avx2 avx512f" 

所有工作,但如果使用空格,则参数应该用引号括起来或通过反斜杠转义。

--cpu-baseline结合了所有暗示的 CPU 功能,例如:

代码语言:javascript复制
python setup.py build --cpu-baseline=sse42
# equivalent to
python setup.py build --cpu-baseline="sse sse2 sse3 ssse3 sse41 popcnt sse42" 

如果编译器本地标志-march=native-xHost/QxHost通过环境变量CFLAGS启用,则--cpu-baseline将被视为“本地”:

代码语言:javascript复制
export CFLAGS="-march=native"
python setup.py install --user
# is equivalent to
python setup.py build --cpu-baseline=native install --user 

--cpu-baseline会将任何指定的不受目标平台或编译器支持的功能转义,而不是引发致命错误。

注意

由于--cpu-baseline结合了所有暗示的功能,将启用最大支持的暗示功能,而不是转义所有功能。例如:

代码语言:javascript复制
# Requesting `AVX2,FMA3` but the compiler only support **SSE** features
python setup.py build --cpu-baseline="avx2 fma3"
# is equivalent to
python setup.py build --cpu-baseline="sse sse2 sse3 ssse3 sse41 popcnt sse42" 

--cpu-dispatch 不包含任何暗示的 CPU 特性,因此除非你想禁用其中一个或全部特性,否则必须添加它们:

代码语言:javascript复制
# Only dispatches AVX2 and FMA3
python setup.py build --cpu-dispatch=avx2,fma3
# Dispatches AVX and SSE features
python setup.py build --cpu-baseline=ssse3,sse41,sse42,avx,avx2,fma3 

--cpu-dispatch 会跳过任何指定的基线特性,也会跳过目标平台或编译器不支持的特性,而不会引发致命错误。

最终,您应始终通过构建日志检查最终报告以验证启用的特性。有关更多详细信息,请参阅构建报告。

平台差异

一些特殊条件迫使我们在涉及某些编译器或架构时将某些特性链接在一起,导致无法单独构建它们。

这些条件可以分为两部分,如下所示:

架构兼容性

需要对一些已确保在同一架构的后续世代中支持的 CPU 特性进行对齐的情况,有些情况如下:

  • 在 ppc64le 上,VSX(ISA 2.06)VSX2(ISA 2.07) 相���暗示,因为支持小端模式的第一代是 Power-8(ISA 2.07)
  • 在 AArch64 上,NEON NEON_FP16 NEON_VFPV4 ASIMD 相互暗示,因为它们是硬件基线的一部分。

例如:

代码语言:javascript复制
# On ARMv8/A64, specify NEON is going to enable Advanced SIMD
# and all predecessor extensions
python setup.py build --cpu-baseline=neon
# which equivalent to
python setup.py build --cpu-baseline="neon neon_fp16 neon_vfpv4 asimd" 

注意

请仔细查看支持的特性,以确定彼此暗示的特性。

编译兼容性

一些编译器不提供对所有 CPU 特性的独立支持。例如Intel的编译器不为AVX2FMA3提供单独的标志,这是有道理的,因为所有支持AVX2的 Intel CPU 也支持FMA3,但这种方法与其他x86 CPU(如AMDVIA)不兼容。

例如:

代码语言:javascript复制
# Specify AVX2 will force enables FMA3 on Intel compilers
python setup.py build --cpu-baseline=avx2
# which equivalent to
python setup.py build --cpu-baseline="avx2 fma3" 

以下表格仅显示一些编译器对通用上下文施加的差异,这些差异在支持的特性表格中已经显示:

注意

有删除线的特性名称表示不支持的 CPU 特性。

在 x86::Intel 编译器

名称

暗示

收集

FMA3

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C AVX2

AVX2

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3

AVX512F

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512CD

XOP

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX

FMA4

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX

AVX512_SPR

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX AVX512_CLX AVX512_CNL AVX512_ICL

AVX512FP16

在 x86::Microsoft Visual C/C

名称

暗示

收集

FMA3

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C AVX2

AVX2

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3

AVX512F

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512CD AVX512_SKX

AVX512CD

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512_SKX

AVX512_KNL

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD

AVX512ER AVX512PF

AVX512_KNM

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_KNL

AVX5124FMAPS AVX5124VNNIW AVX512VPOPCNTDQ

| AVX512_SPR | SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX AVX512_CLX AVX512_CNL AVX512_ICL | AVX512FP16 | ## 构建报告

在大多数情况下,CPU 构建选项不会产生导致构建挂起的致命错误。在构建日志中可能出现的大多数错误都是由于编译器缺少某些预期的 CPU 功能而产生的严重警告。

因此,我们强烈建议检查最终的报告日志,了解启用了哪些 CPU 功能以及哪些没有启用。

您可以在构建日志的末尾找到 CPU 优化的最终报告,以下是在 x86_64/gcc 上的展示方式:

代码语言:javascript复制
########### EXT COMPILER OPTIMIZATION ###########
Platform  :
  Architecture:  x64
  Compiler  :  gcc

CPU  baseline  :
  Requested  :  'min'
  Enabled  :  SSE  SSE2  SSE3
  Flags  :  -msse  -msse2  -msse3
  Extra  checks:  none

CPU  dispatch  :
  Requested  :  'max -xop -fma4'
  Enabled  :  SSSE3  SSE41  POPCNT  SSE42  AVX  F16C  FMA3  AVX2  AVX512F  AVX512CD  AVX512_KNL  AVX512_KNM  AVX512_SKX  AVX512_CLX  AVX512_CNL  AVX512_ICL
  Generated  :
  :
  SSE41  :  SSE  SSE2  SSE3  SSSE3
  Flags  :  -msse  -msse2  -msse3  -mssse3  -msse4.1
  Extra  checks:  none
  Detect  :  SSE  SSE2  SSE3  SSSE3  SSE41
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_arithmetic.dispatch.c
  :  numpy/core/src/umath/_umath_tests.dispatch.c
  :
  SSE42  :  SSE  SSE2  SSE3  SSSE3  SSE41  POPCNT
  Flags  :  -msse  -msse2  -msse3  -mssse3  -msse4.1  -mpopcnt  -msse4.2
  Extra  checks:  none
  Detect  :  SSE  SSE2  SSE3  SSSE3  SSE41  POPCNT  SSE42
  :  build/src.linux-x86_64-3.9/numpy/core/src/_simd/_simd.dispatch.c
  :
  AVX2  :  SSE  SSE2  SSE3  SSSE3  SSE41  POPCNT  SSE42  AVX  F16C
  Flags  :  -msse  -msse2  -msse3  -mssse3  -msse4.1  -mpopcnt  -msse4.2  -mavx  -mf16c  -mavx2
  Extra  checks:  none
  Detect  :  AVX  F16C  AVX2
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_arithm_fp.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_arithmetic.dispatch.c
  :  numpy/core/src/umath/_umath_tests.dispatch.c
  :
  (FMA3  AVX2)  :  SSE  SSE2  SSE3  SSSE3  SSE41  POPCNT  SSE42  AVX  F16C
  Flags  :  -msse  -msse2  -msse3  -mssse3  -msse4.1  -mpopcnt  -msse4.2  -mavx  -mf16c  -mfma  -mavx2
  Extra  checks:  none
  Detect  :  AVX  F16C  FMA3  AVX2
  :  build/src.linux-x86_64-3.9/numpy/core/src/_simd/_simd.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_exponent_log.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_trigonometric.dispatch.c
  :
  AVX512F  :  SSE  SSE2  SSE3  SSSE3  SSE41  POPCNT  SSE42  AVX  F16C  FMA3  AVX2
  Flags  :  -msse  -msse2  -msse3  -mssse3  -msse4.1  -mpopcnt  -msse4.2  -mavx  -mf16c  -mfma  -mavx2  -mavx512f
  Extra  checks:  AVX512F_REDUCE
  Detect  :  AVX512F
  :  build/src.linux-x86_64-3.9/numpy/core/src/_simd/_simd.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_arithm_fp.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_arithmetic.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_exponent_log.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_trigonometric.dispatch.c
  :
  AVX512_SKX  :  SSE  SSE2  SSE3  SSSE3  SSE41  POPCNT  SSE42  AVX  F16C  FMA3  AVX2  AVX512F  AVX512CD
  Flags  :  -msse  -msse2  -msse3  -mssse3  -msse4.1  -mpopcnt  -msse4.2  -mavx  -mf16c  -mfma  -mavx2  -mavx512f  -mavx512cd  -mavx512vl  -mavx512bw  -mavx512dq
  Extra  checks:  AVX512BW_MASK  AVX512DQ_MASK
  Detect  :  AVX512_SKX
  :  build/src.linux-x86_64-3.9/numpy/core/src/_simd/_simd.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_arithmetic.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_exponent_log.dispatch.c
CCompilerOpt.cache_flush[804]  :  write  cache  to  path  ->  /home/seiko/work/repos/numpy/build/temp.linux-x86_64-3.9/ccompiler_opt_cache_ext.py

########### CLIB COMPILER OPTIMIZATION ###########
Platform  :
  Architecture:  x64
  Compiler  :  gcc

CPU  baseline  :
  Requested  :  'min'
  Enabled  :  SSE  SSE2  SSE3
  Flags  :  -msse  -msse2  -msse3
  Extra  checks:  none

CPU  dispatch  :
  Requested  :  'max -xop -fma4'
  Enabled  :  SSSE3  SSE41  POPCNT  SSE42  AVX  F16C  FMA3  AVX2  AVX512F  AVX512CD  AVX512_KNL  AVX512_KNM  AVX512_SKX  AVX512_CLX  AVX512_CNL  AVX512_ICL
  Generated  :  none 

对于build_extbuild_clib的每个单独报告都包含几个部分,每个部分都有几个值,表示以下内容:

平台

  • 架构:目标 CPU 的架构名称。它应该是x86x64ppc64ppc64learmhfaarch64s390xunknown中的一个。
  • 编译器:编译器名称。它应该是 gcc、clang、msvc、icc、iccw 或类 Unix 的其中一个。

CPU 基线

  • 请求的:作为--cpu-baseline的特定功能和选项。
  • 已启用:最终启用的 CPU 功能集。
  • 标志:用于编译所有 NumPy C/C 源文件的编译器标志,除了用于生成分派功能的二进制对象的临时源文件。
  • 额外检查:激活与已启用功能相关的某些功能或内部函数的列表,对于开发 SIMD 内核时进行调试非常有用。

CPU 分派

  • 请求的:作为--cpu-dispatch的特定功能和选项。
  • 已启用:最终启用的 CPU 功能集。
  • 生成的:在此属性的下一行的开头,显示已生成优化的功能,以几个部分的形式显示,具有类似属性的解释如下:
    • 一个或多个分派功能:隐含的 CPU 功能。
    • 标志:用于这些功能的编译器标志。
    • 额外检查:类似于基线,但适用于这些分派功能。
    • 检测:需要在运行时检测以执行生成的优化的一组 CPU 功能。
    • 在上述属性之后并以单独一行的‘:’结尾的行,代表定义生成的优化的 c/c 源文件的路径。 ## 运行时分派

导入 NumPy 会触发对可分派功能集中的可用 CPU 功能进行扫描。这可以通过将环境变量NPY_DISABLE_CPU_FEATURES设置为逗号、制表符或空格分隔的功能列表来进一步限制。如果解析失败或未启用该功能,将引发错误。例如,在x86_64上,这将禁用AVX2FMA3

代码语言:javascript复制
NPY_DISABLE_CPU_FEATURES="AVX2,FMA3" 

如果特性不可用,将发出警告。

描述

以下选项主要用于更改针对特定 CPU 特性的优化的默认行为:

  • --cpu-baseline:所需 CPU 特性的最小集。 默认值为min,提供可以安全运行在处理器系列内广泛范围平台上的最小 CPU 特性。 注意 在运行时,如果目标 CPU 不支持任何指定特性,则 NumPy 模块将无法加载(引发 Python 运行时错误)。
  • --cpu-dispatch:分派的一组额外的 CPU 特性。 默认值为max -xop -fma4,启用所有 CPU 特性,除了 AMD 遗留特性(在 X86 的情况下)。 注意 在运行时,NumPy 模块将跳过目标 CPU 中不可用的任何指定特性。

这些选项可通过distutils命令distutils.command.builddistutils.command.build_clibdistutils.command.build_ext访问,它们接受一组 CPU 特性或收集几个特性的特性组或特殊选项执行一系列过程。

注意

如果用户未指定build_clibbuild_ext,则将使用build的参数,这也包含默认值。

同时自定义build_extbuild_clib

代码语言:javascript复制
cd /path/to/numpy
python setup.py build --cpu-baseline="avx2 fma3" install --user 

仅自定义build_ext

代码语言:javascript复制
cd /path/to/numpy
python setup.py build_ext --cpu-baseline="avx2 fma3" install --user 

仅自定义build_clib

代码语言:javascript复制
cd /path/to/numpy
python setup.py build_clib --cpu-baseline="avx2 fma3" install --user 

您还可以通过 PIP 命令自定义 CPU/构建选项:

代码语言:javascript复制
pip install --no-use-pep517 --global-option=build 
--global-option="--cpu-baseline=avx2 fma3" 
--global-option="--cpu-dispatch=max" ./ 

快速开始

通常,默认设置往往不会强加一些可能在一些旧处理器上不可用的 CPU 特性。提高基线特性的上限通常会提高性能,也可能减小二进制文件大小。

以下是可能需要更改默认设置的最常见情况:

我正在为本地使用构建 NumPy

我也不打算将构建导出给其他用户或针对与主机不同的 CPU。

本机设置为基线,或者在您的平台不支持本机选项的情况下手动指定 CPU 特性:

代码语言:javascript复制
python setup.py build --cpu-baseline="native" bdist 

对于这种情况,使用额外的 CPU 特性构建 NumPy 并不是必要的,因为所有支持的特性已经在基线特性中定义:

代码语言:javascript复制
python setup.py build --cpu-baseline=native --cpu-dispatch=none bdist 

注意

如果主机平台不支持本机,将引发致命错误。

我不想支持x86架构的旧处理器

由于如今大多数 CPU 至少支持AVXF16C特性,您可以使用:

代码语言:javascript复制
python setup.py build --cpu-baseline="avx f16c" bdist 

注意

--cpu-baseline强制组合所有隐含的特性,因此不需要添加 SSE 特性。

我遇到了与上述相同的情况,但是使用ppc64架构

然后将基线特性的上限提高到 Power8:

代码语言:javascript复制
python setup.py build --cpu-baseline="vsx2" bdist 
遇到AVX512特性的问题?

您可能对包含AVX512或任何其他 CPU 特性有所保留,并希望从分派的特性中排除:

代码语言:javascript复制
python setup.py build --cpu-dispatch="max -avx512f -avx512cd 
-avx512_knl -avx512_knm -avx512_skx -avx512_clx -avx512_cnl -avx512_icl" 
bdist 
我正在为本地使用构建 NumPy

我不打算将构建导出给其他用户或针对与主机不同的 CPU 进行目标定位。

设置native为基线,或者在您的平台不支持选项native的情况下手动指定 CPU 特性:

代码语言:javascript复制
python setup.py build --cpu-baseline="native" bdist 

对于这种情况,构建 NumPy 时不需要额外的 CPU 特性,因为所有支持的特性已经在基线特性中定义:

代码语言:javascript复制
python setup.py build --cpu-baseline=native --cpu-dispatch=none bdist 

注意

如果主机平台不支持native,将会引发致命错误。

我不想支持x86架构的旧处理器

由于大多数 CPU 现在至少支持AVXF16C特性,您可以使用:

代码语言:javascript复制
python setup.py build --cpu-baseline="avx f16c" bdist 

注意

--cpu-baseline强制组合所有暗示的特性,因此不需要添加 SSE 特性。

我遇到了与上述相同的情况,但是使用ppc64架构

然后将基线特性的上限提高到 Power8:

代码语言:javascript复制
python setup.py build --cpu-baseline="vsx2" bdist 
遇到AVX512特性的问题?

您可能对包含AVX512或任何其他 CPU 特性有所保留,并希望从分派的特性中排除:

代码语言:javascript复制
python setup.py build --cpu-dispatch="max -avx512f -avx512cd 
-avx512_knl -avx512_knm -avx512_skx -avx512_clx -avx512_cnl -avx512_icl" 
bdist 

支持的特性

特性的名称可以表示一个特性或一组特性,如下表所示,支持的特性取决于最低的兴趣:

注意

以下特性可能不被所有编译器支持,而且一些编译器在涉及AVX512AVX2FMA3等特性时可能会产生不同的暗示特性集。有关更多详细信息,请参阅平台差异。

在 x86 上

名称

意味着

收集

SSE

SSE2

SSE2

SSE

SSE3

SSE SSE2

SSSE3

SSE SSE2 SSE3

SSE41

SSE SSE2 SSE3 SSSE3

POPCNT

SSE SSE2 SSE3 SSSE3 SSE41

SSE42

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT

AVX

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42

XOP

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX

FMA4

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX

F16C

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX

FMA3

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C

AVX2

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C

AVX512F

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2

AVX512CD

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F

AVX512_KNL

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD

AVX512ER AVX512PF

AVX512_KNM

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_KNL

AVX5124FMAPS AVX5124VNNIW AVX512VPOPCNTDQ

AVX512_SKX

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD

AVX512VL AVX512BW AVX512DQ

AVX512_CLX

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX

AVX512VNNI

AVX512_CNL

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX

AVX512IFMA AVX512VBMI

AVX512_ICL

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX AVX512_CLX AVX512_CNL

AVX512VBMI2 AVX512BITALG AVX512VPOPCNTDQ

AVX512_SPR

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX AVX512_CLX AVX512_CNL AVX512_ICL

AVX512FP16

在 IBM/POWER 大端

名称

意味着

VSX

VSX2

VSX

VSX3

VSX VSX2

VSX4

VSX VSX2 VSX3

在 IBM/POWER 小端

名称

意味着

VSX

VSX2

VSX2

VSX

VSX3

VSX VSX2

VSX4

VSX VSX2 VSX3

在 ARMv7/A32

名称

意味着

NEON

NEON_FP16

NEON

NEON_VFPV4

NEON NEON_FP16

ASIMD

NEON NEON_FP16 NEON_VFPV4

ASIMDHP

NEON NEON_FP16 NEON_VFPV4 ASIMD

ASIMDDP

NEON NEON_FP16 NEON_VFPV4 ASIMD

ASIMDFHM

NEON NEON_FP16 NEON_VFPV4 ASIMD ASIMDHP

在 ARMv8/A64

名称

意味着

NEON

NEON_FP16 NEON_VFPV4 ASIMD

NEON_FP16

NEON NEON_VFPV4 ASIMD

NEON_VFPV4

NEON NEON_FP16 ASIMD

ASIMD

NEON NEON_FP16 NEON_VFPV4

ASIMDHP

NEON NEON_FP16 NEON_VFPV4 ASIMD

ASIMDDP

NEON NEON_FP16 NEON_VFPV4 ASIMD

ASIMDFHM

NEON NEON_FP16 NEON_VFPV4 ASIMD ASIMDHP

在 IBM/ZSYSTEM(S390X)

名称

意味着

VX

VXE

VX

VXE2

VX VXE

在 x86

名称

意味着

收集

SSE

SSE2

SSE2

SSE

SSE3

SSE SSE2

SSSE3

SSE SSE2 SSE3

SSE41

SSE SSE2 SSE3 SSSE3

POPCNT

SSE SSE2 SSE3 SSSE3 SSE41

SSE42

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT

AVX

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42

XOP

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX

FMA4

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX

F16C

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX

FMA3

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C

AVX2

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C

AVX512F

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2

AVX512CD

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F

AVX512_KNL

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD

AVX512ER AVX512PF

AVX512_KNM

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_KNL

AVX5124FMAPS AVX5124VNNIW AVX512VPOPCNTDQ

AVX512_SKX

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD

AVX512VL AVX512BW AVX512DQ

AVX512_CLX

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX

AVX512VNNI

AVX512_CNL

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX

AVX512IFMA AVX512VBMI

AVX512_ICL

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX AVX512_CLX AVX512_CNL

AVX512VBMI2 AVX512BITALG AVX512VPOPCNTDQ

AVX512_SPR

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX AVX512_CLX AVX512_CNL AVX512_ICL

AVX512FP16

在 IBM/POWER 大端

名称

含义

VSX

VSX2

VSX

VSX3

VSX VSX2

VSX4

VSX VSX2 VSX3

在 IBM/POWER 小端

名称

含义

VSX

VSX2

VSX2

VSX

VSX3

VSX VSX2

VSX4

VSX VSX2 VSX3

在 ARMv7/A32

名称

含义

NEON

NEON_FP16

NEON

NEON_VFPV4

NEON NEON_FP16

ASIMD

NEON NEON_FP16 NEON_VFPV4

ASIMDHP

NEON NEON_FP16 NEON_VFPV4 ASIMD

ASIMDDP

NEON NEON_FP16 NEON_VFPV4 ASIMD

ASIMDFHM

NEON NEON_FP16 NEON_VFPV4 ASIMD ASIMDHP

在 ARMv8/A64

名称

含义

NEON

NEON_FP16 NEON_VFPV4 ASIMD

NEON_FP16

NEON NEON_VFPV4 ASIMD

NEON_VFPV4

NEON NEON_FP16 ASIMD

ASIMD

NEON NEON_FP16 NEON_VFPV4

ASIMDHP

NEON NEON_FP16 NEON_VFPV4 ASIMD

ASIMDDP

NEON NEON_FP16 NEON_VFPV4 ASIMD

ASIMDFHM

NEON NEON_FP16 NEON_VFPV4 ASIMD ASIMDHP

在 IBM/ZSYSTEM(S390X)

名称

含义

VX

VXE

VX

VXE2

VX VXE

特殊选项

  • NONE: 不启用任何功能。
  • NATIVE: 启用主机 CPU 支持的所有 CPU 功能,此操作基于编译器标志(-march=native-xHost/QxHost
  • MIN: 启用最小的 CPU 功能,可以安全地在各种平台上运行: 对架构含义x86(32 位模式)SSE SSE2x86_64SSE SSE2 SSE3IBM/POWER(大端模式)NONEIBM/POWER(小端模式)VSX VSX2ARMHFNONEARM64 A.K. AARCH64NEON NEON_FP16 NEON_VFPV4 ASIMDIBM/ZSYSTEM(S390X)NONE
  • MAX: 通过编译器和平台启用所有支持的 CPU 特性。
  • Operators-/ :删除或添加特性,与选项MAXMINNATIVE一起使用。

行为

CPU 特性和其他选项不区分大小写,例如:

代码语言:javascript复制
python setup.py build --cpu-dispatch="SSE41 avx2 FMA3" 

请求的优化顺序无关紧要:

代码语言:javascript复制
python setup.py build --cpu-dispatch="SSE41 AVX2 FMA3"
# equivalent to
python setup.py build --cpu-dispatch="FMA3 AVX2 SSE41" 

分隔符可以使用逗号、空格或‘ ’,例如:

代码语言:javascript复制
python setup.py build --cpu-dispatch="avx2 avx512f"
# or
python setup.py build --cpu-dispatch=avx2,avx512f
# or
python setup.py build --cpu-dispatch="avx2 avx512f" 

所有都有效,但如果使用了空格,则参数应该用引号括起来或通过反斜杠进行转义。

--cpu-baseline结合了所有隐含的 CPU 特性,例如:

代码语言:javascript复制
python setup.py build --cpu-baseline=sse42
# equivalent to
python setup.py build --cpu-baseline="sse sse2 sse3 ssse3 sse41 popcnt sse42" 

如果通过环境变量CFLAGS启用编译器本机标志-march=native-xHost/QxHost,则--cpu-baseline将被视为“本机”:

代码语言:javascript复制
export CFLAGS="-march=native"
python setup.py install --user
# is equivalent to
python setup.py build --cpu-baseline=native install --user 

--cpu-baseline逃避任何指定的特性,如果目标平台或编译器不支持,则不会引发致命错误。

注意

由于--cpu-baseline结合了所有隐含的特性,所以将启用隐含特性中支持的最大特性,而不是逃避所有特性。例如:

代码语言:javascript复制
# Requesting `AVX2,FMA3` but the compiler only support **SSE** features
python setup.py build --cpu-baseline="avx2 fma3"
# is equivalent to
python setup.py build --cpu-baseline="sse sse2 sse3 ssse3 sse41 popcnt sse42" 

--cpu-dispatch不结合任何隐含的 CPU 特性,因此除非您想要禁用其中一个或全部特性,否则必须添加它们:

代码语言:javascript复制
# Only dispatches AVX2 and FMA3
python setup.py build --cpu-dispatch=avx2,fma3
# Dispatches AVX and SSE features
python setup.py build --cpu-baseline=ssse3,sse41,sse42,avx,avx2,fma3 

--cpu-dispatch逃避任何指定的基线特性,同时也逃避目标平台或编译器不支持的任何特性,而不会引发致命错误。

最终,您应该始终通过构建日志检查最终报告以验证启用的特性。有关更多详细信息,请参阅构建报告。

平台差异

在某些特殊情况下,当涉及到某些编译器或架构时,我们被迫将某些特性链接在一起,导致无法单独构建它们。

这些条件可以分为两部分,如下所示:

架构兼容性

需要对某些 CPU 特性进行对齐,这些特性被保证在同一架构的连续几代中都会支持,一些情况如下:

  • 在 ppc64le 上,VSX(ISA 2.06)VSX2(ISA 2.07)互相隐含,因为第一代支持小端模式的是 Power-8(ISA 2.07)
  • 在 AArch64 上,NEON NEON_FP16 NEON_VFPV4 ASIMD互相隐含,因为它们是硬件基线的一部分。

例如:

代码语言:javascript复制
# On ARMv8/A64, specify NEON is going to enable Advanced SIMD
# and all predecessor extensions
python setup.py build --cpu-baseline=neon
# which equivalent to
python setup.py build --cpu-baseline="neon neon_fp16 neon_vfpv4 asimd" 

注意

请仔细查看支持的特性,以确定互相隐含的特性。

编译兼容性

一些编译器不提供对所有 CPU 特性的独立支持。例如,英特尔的编译器不为AVX2FMA3提供单独的标志,这是有道理的,因为所有带有AVX2的英特尔 CPU 也支持FMA3,但这种方法与其他x86 CPU(如AMDVIA)不兼容。

例如:

代码语言:javascript复制
# Specify AVX2 will force enables FMA3 on Intel compilers
python setup.py build --cpu-baseline=avx2
# which equivalent to
python setup.py build --cpu-baseline="avx2 fma3" 

以下表格仅显示一些编译器对支持特性表中显示的一般上下文施加的差异:

带有删除线的特性名称代表不支持的 CPU 特性。

在 x86::Intel 编译器上

名称

暗示

收集

FMA3

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C AVX2

AVX2

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3

AVX512F

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512CD

XOP

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX

FMA4

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX

AVX512_SPR

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX AVX512_CLX AVX512_CNL AVX512_ICL AVX512FP16

在 x86::Microsoft Visual C/C 上

名称

暗示

收集

FMA3

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C AVX2

AVX2

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3

AVX512F

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512CD AVX512_SKX

AVX512CD

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512_SKX

AVX512_KNL

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512ER AVX512PF

AVX512_KNM

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_KNL AVX5124FMAPS AVX5124VNNIW AVX512VPOPCNTDQ

AVX512_SPR

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX AVX512_CLX AVX512_CNL AVX512_ICL AVX512FP16

在 x86::Intel 编译器上

名称

暗示

收集

FMA3

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C AVX2

AVX2

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3

AVX512F

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512CD

XOP

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX

FMA4

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX

AVX512_SPR

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX AVX512_CLX AVX512_CNL AVX512_ICL AVX512FP16

在 x86::Microsoft Visual C/C 上

名称

暗示

收集

FMA3

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C AVX2

AVX2

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3

AVX512F

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512CD AVX512_SKX

AVX512CD

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512_SKX

AVX512_KNL

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512ER AVX512PF

AVX512_KNM

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX5124FMAPS AVX5124VNNIW AVX512VPOPCNTDQ

AVX512_SPR

SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_SKX AVX512_CLX AVX512_CNL AVX512_ICL AVX512FP16

构建报告

在大多数情况下,CPU 构建选项不会产生导致构建挂起的致命错误。在构建日志中可能出现的大多数错误都是由于编译器缺少某些预期的 CPU 功能而产生的严重警告。

因此,我们强烈建议检查最终报告日志,了解启用了哪些 CPU 功能以及哪些没有。

您可以在构建日志的末尾找到 CPU 优化的最终报告,以下是在 x86_64/gcc 上的展示方式:

代码语言:javascript复制
########### EXT COMPILER OPTIMIZATION ###########
Platform  :
  Architecture:  x64
  Compiler  :  gcc

CPU  baseline  :
  Requested  :  'min'
  Enabled  :  SSE  SSE2  SSE3
  Flags  :  -msse  -msse2  -msse3
  Extra  checks:  none

CPU  dispatch  :
  Requested  :  'max -xop -fma4'
  Enabled  :  SSSE3  SSE41  POPCNT  SSE42  AVX  F16C  FMA3  AVX2  AVX512F  AVX512CD  AVX512_KNL  AVX512_KNM  AVX512_SKX  AVX512_CLX  AVX512_CNL  AVX512_ICL
  Generated  :
  :
  SSE41  :  SSE  SSE2  SSE3  SSSE3
  Flags  :  -msse  -msse2  -msse3  -mssse3  -msse4.1
  Extra  checks:  none
  Detect  :  SSE  SSE2  SSE3  SSSE3  SSE41
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_arithmetic.dispatch.c
  :  numpy/core/src/umath/_umath_tests.dispatch.c
  :
  SSE42  :  SSE  SSE2  SSE3  SSSE3  SSE41  POPCNT
  Flags  :  -msse  -msse2  -msse3  -mssse3  -msse4.1  -mpopcnt  -msse4.2
  Extra  checks:  none
  Detect  :  SSE  SSE2  SSE3  SSSE3  SSE41  POPCNT  SSE42
  :  build/src.linux-x86_64-3.9/numpy/core/src/_simd/_simd.dispatch.c
  :
  AVX2  :  SSE  SSE2  SSE3  SSSE3  SSE41  POPCNT  SSE42  AVX  F16C
  Flags  :  -msse  -msse2  -msse3  -mssse3  -msse4.1  -mpopcnt  -msse4.2  -mavx  -mf16c  -mavx2
  Extra  checks:  none
  Detect  :  AVX  F16C  AVX2
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_arithm_fp.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_arithmetic.dispatch.c
  :  numpy/core/src/umath/_umath_tests.dispatch.c
  :
  (FMA3  AVX2)  :  SSE  SSE2  SSE3  SSSE3  SSE41  POPCNT  SSE42  AVX  F16C
  Flags  :  -msse  -msse2  -msse3  -mssse3  -msse4.1  -mpopcnt  -msse4.2  -mavx  -mf16c  -mfma  -mavx2
  Extra  checks:  none
  Detect  :  AVX  F16C  FMA3  AVX2
  :  build/src.linux-x86_64-3.9/numpy/core/src/_simd/_simd.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_exponent_log.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_trigonometric.dispatch.c
  :
  AVX512F  :  SSE  SSE2  SSE3  SSSE3  SSE41  POPCNT  SSE42  AVX  F16C  FMA3  AVX2
  Flags  :  -msse  -msse2  -msse3  -mssse3  -msse4.1  -mpopcnt  -msse4.2  -mavx  -mf16c  -mfma  -mavx2  -mavx512f
  Extra  checks:  AVX512F_REDUCE
  Detect  :  AVX512F
  :  build/src.linux-x86_64-3.9/numpy/core/src/_simd/_simd.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_arithm_fp.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_arithmetic.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_exponent_log.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_trigonometric.dispatch.c
  :
  AVX512_SKX  :  SSE  SSE2  SSE3  SSSE3  SSE41  POPCNT  SSE42  AVX  F16C  FMA3  AVX2  AVX512F  AVX512CD
  Flags  :  -msse  -msse2  -msse3  -mssse3  -msse4.1  -mpopcnt  -msse4.2  -mavx  -mf16c  -mfma  -mavx2  -mavx512f  -mavx512cd  -mavx512vl  -mavx512bw  -mavx512dq
  Extra  checks:  AVX512BW_MASK  AVX512DQ_MASK
  Detect  :  AVX512_SKX
  :  build/src.linux-x86_64-3.9/numpy/core/src/_simd/_simd.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_arithmetic.dispatch.c
  :  build/src.linux-x86_64-3.9/numpy/core/src/umath/loops_exponent_log.dispatch.c
CCompilerOpt.cache_flush[804]  :  write  cache  to  path  ->  /home/seiko/work/repos/numpy/build/temp.linux-x86_64-3.9/ccompiler_opt_cache_ext.py

########### CLIB COMPILER OPTIMIZATION ###########
Platform  :
  Architecture:  x64
  Compiler  :  gcc

CPU  baseline  :
  Requested  :  'min'
  Enabled  :  SSE  SSE2  SSE3
  Flags  :  -msse  -msse2  -msse3
  Extra  checks:  none

CPU  dispatch  :
  Requested  :  'max -xop -fma4'
  Enabled  :  SSSE3  SSE41  POPCNT  SSE42  AVX  F16C  FMA3  AVX2  AVX512F  AVX512CD  AVX512_KNL  AVX512_KNM  AVX512_SKX  AVX512_CLX  AVX512_CNL  AVX512_ICL
  Generated  :  none 

对于build_extbuild_clib中的每个部分都有一个单独的报告,每个部分都有几个值,表示以下内容:

平台:

  • 架构:目标 CPU 的架构名称。应该是x86x64ppc64ppc64learmhfaarch64s390xunknown中的一个。
  • 编译器:编译器名称。应该是 gcc、clang、msvc、icc、iccw 或类 Unix 的其中一个。

CPU 基线:

  • 请求:特定的--cpu-baseline功能和选项不变。
  • 已启用:最终启用的 CPU 功能集。
  • 标志:用于编译所有 NumPy *C/C *源文件的编译器标志,除了用于生成分派功能的二进制对象的临时源文件。
  • 额外检查:激活与启用功能相关的某些功能或内部检查的列表,对于开发 SIMD 内核时进行调试非常有用。

CPU 分派:

  • 请求:特定的--cpu-dispatch功能和选项不变。
  • 已启用:最终启用的 CPU 功能集。
  • 生成的:在此属性的下一行开头,显示已生成优化的功能,以几个类似属性的部分形式显示,解释如下:
    • 一个或多个分派的功能:隐含的 CPU 功能。
    • 标志:用于这些功能的编译器标志。
    • 额外检查:类似于基线,但适用于这些分派功能。
    • 检测:需要在运行时检测的 CPU 功能集,以执行生成的优化。
    • 在上述属性之后以及以单独一行的‘:’结尾的行代表定义生成优化的 c/c 源文件的路径。

运行时分派

导入 NumPy 会触发从可分派功能集中扫描可用 CPU 功能。这可以通过将环境变量NPY_DISABLE_CPU_FEATURES设置为以逗号、制表符或空格分隔的功能列表来进一步限制。如果解析失败或未启用该功能,将引发错误。例如,在x86_64上,这将禁用AVX2FMA3

代码语言:javascript复制
NPY_DISABLE_CPU_FEATURES="AVX2,FMA3" 

如果该功能不可用,将发出警告。

CPU 调度器是如何工作的?

原文:numpy.org/doc/1.26/reference/simd/how-it-works.html

NumPy 调度器基于多源编译,这意味着采用一定的源代码,并使用不同的编译器标志以及不同的C定义来多次进行编译,这些定义影响代码路径。这使得每个编译后的对象可以根据所需的优化启用某些指令集,并最终链接返回的对象在一起。

这种机制应该支持所有编译器,并且不需要任何特定于编译器的扩展,但与此同时,它会对正常编译增加一些步骤,下面将对此进行解释。

1- 配置

在开始构建源文件之前,用户通过上述两个命令行参数配置所需的优化:

  • --cpu-baseline:所需优化的最小集合。
  • --cpu-dispatch:附加优化的调度集合。

2- 发现环境

在这一部分,我们检查编译器和平台架构,并缓存一些中间结果以加快重新构建的速度。

3- 验证所请求的优化

通过针对编译器进行测试,以及查看编译器根据请求的优化所支持的内容。

4- 生成主配置头文件

生成的头文件 _cpu_dispatch.h 包含了在前一步验证过的所需优化的指令集的所有定义和头文件。

它还包含了额外的 C 定义,用于定义 NumPy 的 Python 模块属性 __cpu_baseline____cpu_dispatch__

这个头文件中有什么内容?

此示例头文件是在 X86 机器上由 gcc 动态生成的。编译器支持--cpu-baseline="sse sse2 sse3"--cpu-dispatch="ssse3 sse41",结果如下。

代码语言:javascript复制
// The header should be located at numpy/numpy/core/src/common/_cpu_dispatch.h
/**NOTE
 ** C definitions prefixed with "NPY_HAVE_" represent
 ** the required optimizations.
 **
 ** C definitions prefixed with 'NPY__CPU_TARGET_' are protected and
 ** shouldn't be used by any NumPy C sources.
 */
/******* baseline features *******/
/** SSE **/
#define NPY_HAVE_SSE 1
#include  <xmmintrin.h>
/** SSE2 **/
#define NPY_HAVE_SSE2 1
#include  <emmintrin.h>
/** SSE3 **/
#define NPY_HAVE_SSE3 1
#include  <pmmintrin.h>

/******* dispatch-able features *******/
#ifdef NPY__CPU_TARGET_SSSE3
  /** SSSE3 **/
  #define NPY_HAVE_SSSE3 1
  #include  <tmmintrin.h>
#endif
#ifdef NPY__CPU_TARGET_SSE41
  /** SSE41 **/
  #define NPY_HAVE_SSE41 1
  #include  <smmintrin.h>
#endif 

基线特性是通过--cpu-baseline配置的所需优化的最小集合。它们没有预处理保护,并且始终开启,这意味着它们可以在任何源代码中使用。

这是否意味着 NumPy 的基础设施将基线特性的编译器标志传递给所有源代码?

当然可以。但是可分发的源代码会被不同对待。

如果用户在构建过程中指定了特定的基线特性,但在运行时机器甚至不支持这些特性,会怎么样?编译后的代码是否会通过这些定义之一被调用,或者也许编译器本身基于提供的命令行编译器标志自动生成/矢量化某些代码片段?

在加载 NumPy 模块时,有一个验证步骤来检测这种行为。它会引发 Python 运行时错误以通知用户。这是为了防止 CPU 达到非法指令错误,导致段错误。

可调度特性 是我们通过 --cpu-dispatch 配置的一组附加优化。它们不会默认激活,并且始终由其他以 NPY__CPU_TARGET_ 为前缀的 C 定义保护。C 定义 NPY__CPU_TARGET_ 仅在 可调度源 内启用。

5- 可调度源和配置语句

可调度源是特殊的 C 文件,可以使用不同的编译器标志和不同的 C 定义进行多次编译。这些会影响代码路径,以便根据每个编译对象顶部必须声明的“配置语句”来启用某些指令集。同时,如果通过命令参数 --disable-optimization 禁用了优化,则可调度源将被视为普通的 C 源。

什么是配置语句?

配置语句是一种组合在一起以确定可调度源所需优化的关键字。

示例:

代码语言:javascript复制
/*@targets avx2 avx512f vsx2 vsx3 asimd asimdhp */
// C code 

这些关键词主要代表了通过 --cpu-dispatch 配置的附加优化,但也可以代表其他选项,例如:

  • 目标组:用于管理可调度源文件外部所需优化的预配置配置语句。
  • 策略:一组选项,用于更改默认行为或强制编译器执行某些操作。
  • “baseline”:一个唯一的关键字,表示通过 --cpu-baseline 配置的最小优化。

Numpy 的基础设施处理可调度源有四个步骤

(A) 认知:就像源模板和 F2PY 一样,可调度的源文件需要一个特殊的扩展名 *.dispatch.c 来标记 C 可调度的源文件,而对于 C 则是 *.dispatch.cpp*.dispatch.cxx 注意:目前不支持 C 。

(B) 解析和验证:在此步骤中,先前通过上一步筛选的可调度源逐个解析和验证其配置语句,以确定所需的优化。

© 封装:这是 NumPy 基础设施采取的方法,已被证明足够灵活,可以使用不同的 C 定义和标志多次编译单个源,从而影响代码路径。该过程通过为与附加优化相关的每个所需优化创建临时 C 源来实现,其中包含 C 定义的声明,并通过 C 指令 #include 包含相关源。要了解更多细节,请查看以下 AVX512F 代码:

代码语言:javascript复制
/*
 * this definition is used by NumPy utilities as suffixes for the
 * exported symbols
 */
#define NPY__CPU_TARGET_CURRENT AVX512F
/*
 * The following definitions enable
 * definitions of the dispatch-able features that are defined within the main
 * configuration header. These are definitions for the implied features.
 */
#define NPY__CPU_TARGET_SSE
#define NPY__CPU_TARGET_SSE2
#define NPY__CPU_TARGET_SSE3
#define NPY__CPU_TARGET_SSSE3
#define NPY__CPU_TARGET_SSE41
#define NPY__CPU_TARGET_POPCNT
#define NPY__CPU_TARGET_SSE42
#define NPY__CPU_TARGET_AVX
#define NPY__CPU_TARGET_F16C
#define NPY__CPU_TARGET_FMA3
#define NPY__CPU_TARGET_AVX2
#define NPY__CPU_TARGET_AVX512F
// our dispatch-able source
#include  "/the/absuolate/path/of/hello.dispatch.c" 

(D)可调度配置头文件:基础设施为每个可调度源代码生成一个配置头文件,该头文件主要包含两个抽象的C宏,用于通过任何C源代码从生成的对象中调度特定的符号。它还用于前向声明。

生成头文件以可调度源的名称命名,排除扩展名并替换为.h,例如假设我们有一个名为hello.dispatch.c的可调度源代码,其内容如下:

代码语言:javascript复制
// hello.dispatch.c
/*@targets baseline sse42 avx512f */
#include  <stdio.h>
#include  "numpy/utils.h" // NPY_CAT, NPY_TOSTR

#ifndef NPY__CPU_TARGET_CURRENT
  // wrapping the dispatch-able source only happens to the additional optimizations
  // but if the keyword 'baseline' provided within the configuration statements,
  // the infrastructure will add extra compiling for the dispatch-able source by
  // passing it as-is to the compiler without any changes.
  #define CURRENT_TARGET(X) X
  #define NPY__CPU_TARGET_CURRENT baseline // for printing only
#else
  // since we reach to this point, that's mean we're dealing with
  // the additional optimizations, so it could be SSE42 or AVX512F
  #define CURRENT_TARGET(X) NPY_CAT(NPY_CAT(X, _), NPY__CPU_TARGET_CURRENT)
#endif
// Macro 'CURRENT_TARGET' adding the current target as suffux to the exported symbols,
// to avoid linking duplications, NumPy already has a macro called
// 'NPY_CPU_DISPATCH_CURFX' similar to it, located at
// numpy/numpy/core/src/common/npy_cpu_dispatch.h
// NOTE: we tend to not adding suffixes to the baseline exported symbols
void  CURRENT_TARGET(simd_whoami)(const  char  *extra_info)
{
  printf("I'm "  NPY_TOSTR(NPY__CPU_TARGET_CURRENT)  ", %sn",  extra_info);
} 

现在假设您将hello.dispatch.c附加到源代码树上,那么基础设施应该生成一个名为hello.dispatch.h的临时配置头文件,任何源代码都可以访问它,它应该包含以下代码:

代码语言:javascript复制
#ifndef NPY__CPU_DISPATCH_EXPAND_
  // To expand the macro calls in this header
  #define NPY__CPU_DISPATCH_EXPAND_(X) X
#endif
// Undefining the following macros, due to the possibility of including config headers
// multiple times within the same source and since each config header represents
// different required optimizations according to the specified configuration
// statements in the dispatch-able source that derived from it.
#undef NPY__CPU_DISPATCH_BASELINE_CALL
#undef NPY__CPU_DISPATCH_CALL
// nothing strange here, just a normal preprocessor callback
// enabled only if 'baseline' specified within the configuration statements
#define NPY__CPU_DISPATCH_BASELINE_CALL(CB, ...) 
 NPY__CPU_DISPATCH_EXPAND_(CB(__VA_ARGS__))
// 'NPY__CPU_DISPATCH_CALL' is an abstract macro is used for dispatching
// the required optimizations that specified within the configuration statements.
//
// @param CHK, Expected a macro that can be used to detect CPU features
// in runtime, which takes a CPU feature name without string quotes and
// returns the testing result in a shape of boolean value.
// NumPy already has macro called "NPY_CPU_HAVE", which fits this requirement.
//
// @param CB, a callback macro that expected to be called multiple times depending
// on the required optimizations, the callback should receive the following arguments:
//  1- The pending calls of @param CHK filled up with the required CPU features,
//     that need to be tested first in runtime before executing call belong to
//     the compiled object.
//  2- The required optimization name, same as in 'NPY__CPU_TARGET_CURRENT'
//  3- Extra arguments in the macro itself
//
// By default the callback calls are sorted depending on the highest interest
// unless the policy "$keep_sort" was in place within the configuration statements
// see "Dive into the CPU dispatcher" for more clarification.
#define NPY__CPU_DISPATCH_CALL(CHK, CB, ...) 
 NPY__CPU_DISPATCH_EXPAND_(CB((CHK(AVX512F)), AVX512F, __VA_ARGS__)) 
 NPY__CPU_DISPATCH_EXPAND_(CB((CHK(SSE)&&CHK(SSE2)&&CHK(SSE3)&&CHK(SSSE3)&&CHK(SSE41)), SSE41, __VA_ARGS__)) 

根据上述内容使用配置头文件的示例:

代码语言:javascript复制
// NOTE: The following macros are only defined for demonstration purposes only.
// NumPy already has a collections of macros located at
// numpy/numpy/core/src/common/npy_cpu_dispatch.h, that covers all dispatching
// and declarations scenarios.

#include  "numpy/npy_cpu_features.h" // NPY_CPU_HAVE
#include  "numpy/utils.h" // NPY_CAT, NPY_EXPAND

// An example for setting a macro that calls all the exported symbols at once
// after checking if they're supported by the running machine.
#define DISPATCH_CALL_ALL(FN, ARGS) 
 NPY__CPU_DISPATCH_CALL(NPY_CPU_HAVE, DISPATCH_CALL_ALL_CB, FN, ARGS) 
 NPY__CPU_DISPATCH_BASELINE_CALL(DISPATCH_CALL_BASELINE_ALL_CB, FN, ARGS)
// The preprocessor callbacks.
// The same suffixes as we define it in the dispatch-able source.
#define DISPATCH_CALL_ALL_CB(CHECK, TARGET_NAME, FN, ARGS) 
 if (CHECK) { NPY_CAT(NPY_CAT(FN, _), TARGET_NAME) ARGS; }
#define DISPATCH_CALL_BASELINE_ALL_CB(FN, ARGS) 
 FN NPY_EXPAND(ARGS);

// An example for setting a macro that calls the exported symbols of highest
// interest optimization, after checking if they're supported by the running machine.
#define DISPATCH_CALL_HIGH(FN, ARGS) 
 if (0) {} 
 NPY__CPU_DISPATCH_CALL(NPY_CPU_HAVE, DISPATCH_CALL_HIGH_CB, FN, ARGS) 
 NPY__CPU_DISPATCH_BASELINE_CALL(DISPATCH_CALL_BASELINE_HIGH_CB, FN, ARGS)
// The preprocessor callbacks
// The same suffixes as we define it in the dispatch-able source.
#define DISPATCH_CALL_HIGH_CB(CHECK, TARGET_NAME, FN, ARGS) 
 else if (CHECK) { NPY_CAT(NPY_CAT(FN, _), TARGET_NAME) ARGS; }
#define DISPATCH_CALL_BASELINE_HIGH_CB(FN, ARGS) 
 else { FN NPY_EXPAND(ARGS); }

// NumPy has a macro called 'NPY_CPU_DISPATCH_DECLARE' can be used
// for forward declarations any kind of prototypes based on
// 'NPY__CPU_DISPATCH_CALL' and 'NPY__CPU_DISPATCH_BASELINE_CALL'.
// However in this example, we just handle it manually.
void  simd_whoami(const  char  *extra_info);
void  simd_whoami_AVX512F(const  char  *extra_info);
void  simd_whoami_SSE41(const  char  *extra_info);

void  trigger_me(void)
{
  // bring the auto-generated config header
  // which contains config macros 'NPY__CPU_DISPATCH_CALL' and
  // 'NPY__CPU_DISPATCH_BASELINE_CALL'.
  // it is highly recommended to include the config header before executing
  // the dispatching macros in case if there's another header in the scope.
  #include  "hello.dispatch.h"
  DISPATCH_CALL_ALL(simd_whoami,  ("all"))
  DISPATCH_CALL_HIGH(simd_whoami,  ("the highest interest"))
  // An example of including multiple config headers in the same source
  // #include "hello2.dispatch.h"
  // DISPATCH_CALL_HIGH(another_function, ("the highest interest"))
} 

1- 配置

在开始构建源文件之前,通过上述两个命令参数配置所需的优化:

  • --cpu-baseline: 最小集合的必需优化。
  • --cpu-dispatch: 分派的一组额外优化。

2- 发现环境

在此部分,我们检查编译器和平台架构,并缓存一些中间结果以加快重建速度。

3- 验证所请求的优化

通过对它们进行编译器测试,并根据所请求的优化查看编译器可以支持的内容。

4- 生成主配置头文件

生成的头文件_cpu_dispatch.h包含在上一步中验证的所需优化的所有定义和指令集的标头。

它还包含用于定义 NumPy 的 Python 级模块属性__cpu_baseline____cpu_dispatch__的额外 C 定义。

此标题中包含什么?

该示例标题在一个 X86 机器上由 gcc 动态生成。编译器支持--cpu-baseline="sse sse2 sse3"--cpu-dispatch="ssse3 sse41",结果如下。

代码语言:javascript复制
// The header should be located at numpy/numpy/core/src/common/_cpu_dispatch.h
/**NOTE
 ** C definitions prefixed with "NPY_HAVE_" represent
 ** the required optimizations.
 **
 ** C definitions prefixed with 'NPY__CPU_TARGET_' are protected and
 ** shouldn't be used by any NumPy C sources.
 */
/******* baseline features *******/
/** SSE **/
#define NPY_HAVE_SSE 1
#include  <xmmintrin.h>
/** SSE2 **/
#define NPY_HAVE_SSE2 1
#include  <emmintrin.h>
/** SSE3 **/
#define NPY_HAVE_SSE3 1
#include  <pmmintrin.h>

/******* dispatch-able features *******/
#ifdef NPY__CPU_TARGET_SSSE3
  /** SSSE3 **/
  #define NPY_HAVE_SSSE3 1
  #include  <tmmintrin.h>
#endif
#ifdef NPY__CPU_TARGET_SSE41
  /** SSE41 **/
  #define NPY_HAVE_SSE41 1
  #include  <smmintrin.h>
#endif 

基线特性是通过--cpu-baseline配置的最小集合的所需优化。它们没有预处理器保护,并且始终启用,这意味着它们可以在任何源代码中使用。

这是否意味着 NumPy 的基础设施将编译器的基线特性标志传递给所有源代码?

当然,是的。但是可调度源代码会被另外处理。

如果用户在构建过程中指定了某些基线特性,但在运行时机器甚至不支持这些特性怎么办?编译后的代码是否会通过这些定义之一调用,或者编译器是否会根据提供的命令行编译器标志自动生成/矢量化某段代码?

在加载 NumPy 模块期间,会发现这种行为的验证步骤。它会引发 Python 运行时错误通知用户。这是为了防止 CPU 达到非法指令错误而导致段错误。

分发功能是我们通过--cpu-dispatch配置的分发的一组额外优化。它们不会默认激活,并始终由以NPY__CPU_TARGET_为前缀的其他 C 定义保护。C 定义NPY__CPU_TARGET_仅在分发源内启用。

5- 分发源和配置语句

分发源是特殊的C文件,可以使用不同的编译器标志和不同的C定义多次编译。这些影响代码路径,根据“配置语句”启用每个编译对象的某些指令集,这些语句必须在C注释(/**/)中声明,并在每个分发源顶部以特殊标记**@targets开头。同时,如果通过命令参数--disable-optimization禁用优化,则将分发源视为正常的C**源。

什么是配置语句?

配置语句是一种关键字的组合,用于确定分发源所需的优化。

例子:

代码语言:javascript复制
/*@targets avx2 avx512f vsx2 vsx3 asimd asimdhp */
// C code 

这些关键字主要代表通过--cpu-dispatch配置的额外优化,但也可以代表其他选项,如:

  • 目标组:用于管理分发源外部所需优化的预配置配置语句。
  • 策略:用于改变默认行为或强制编译器执行某些操作的选项集合。
  • “baseline”:一个独特的关键字,代表通过--cpu-baseline配置的最小优化

NumPy 的基础结构处理分发源需要经过四个步骤

(A) 识别:就像源模板和 F2PY 一样,分发源文件需要一个特殊的扩展名*.dispatch.c来标记 C 分发源文件,对于 C 为*.dispatch.cpp*.dispatch.cxx 注意:C 目前不受支持。

(B) 解析和验证:在这个步骤中,通过上一步筛选的分发源将按顺序分别由配置语句解析和验证,以确定所需的优化。

© 封装:这是 NumPy 基础设施采用的方法,已经证明足够灵活,可以编译多次相同的源文件,但使用不同的 C 定义和影响代码路径的标志。该过程通过为与额外优化相关的每个必需的优化创建临时 C 源文件来实现,其中包含 C 定义的声明,并通过 C 指令 #include 包含相关源文件。为了更好地说明,请看下面的 AVX512F 代码:

代码语言:javascript复制
/*
 * this definition is used by NumPy utilities as suffixes for the
 * exported symbols
 */
#define NPY__CPU_TARGET_CURRENT AVX512F
/*
 * The following definitions enable
 * definitions of the dispatch-able features that are defined within the main
 * configuration header. These are definitions for the implied features.
 */
#define NPY__CPU_TARGET_SSE
#define NPY__CPU_TARGET_SSE2
#define NPY__CPU_TARGET_SSE3
#define NPY__CPU_TARGET_SSSE3
#define NPY__CPU_TARGET_SSE41
#define NPY__CPU_TARGET_POPCNT
#define NPY__CPU_TARGET_SSE42
#define NPY__CPU_TARGET_AVX
#define NPY__CPU_TARGET_F16C
#define NPY__CPU_TARGET_FMA3
#define NPY__CPU_TARGET_AVX2
#define NPY__CPU_TARGET_AVX512F
// our dispatch-able source
#include  "/the/absuolate/path/of/hello.dispatch.c" 

(D) 可调度配置头文件:基础设施为每个可调度源生成一个配置头文件,该头文件主要包含两个抽象的 C 宏,用于标识生成的对象,以便可以在运行时通过任何 C 源代码调度从生成的对象中的某些符号。它也用于前向声明。

生成的头文件采用可调度源的名称,排除扩展名,并替换为.h,例如假设我们有一个名为 hello.dispatch.c 的可调度源,并包含以下内容:

代码语言:javascript复制
// hello.dispatch.c
/*@targets baseline sse42 avx512f */
#include  <stdio.h>
#include  "numpy/utils.h" // NPY_CAT, NPY_TOSTR

#ifndef NPY__CPU_TARGET_CURRENT
  // wrapping the dispatch-able source only happens to the additional optimizations
  // but if the keyword 'baseline' provided within the configuration statements,
  // the infrastructure will add extra compiling for the dispatch-able source by
  // passing it as-is to the compiler without any changes.
  #define CURRENT_TARGET(X) X
  #define NPY__CPU_TARGET_CURRENT baseline // for printing only
#else
  // since we reach to this point, that's mean we're dealing with
  // the additional optimizations, so it could be SSE42 or AVX512F
  #define CURRENT_TARGET(X) NPY_CAT(NPY_CAT(X, _), NPY__CPU_TARGET_CURRENT)
#endif
// Macro 'CURRENT_TARGET' adding the current target as suffux to the exported symbols,
// to avoid linking duplications, NumPy already has a macro called
// 'NPY_CPU_DISPATCH_CURFX' similar to it, located at
// numpy/numpy/core/src/common/npy_cpu_dispatch.h
// NOTE: we tend to not adding suffixes to the baseline exported symbols
void  CURRENT_TARGET(simd_whoami)(const  char  *extra_info)
{
  printf("I'm "  NPY_TOSTR(NPY__CPU_TARGET_CURRENT)  ", %sn",  extra_info);
} 

假设你已经将 hello.dispatch.c 附加到源树中,那么基础设施应该生成一个临时的配置头文件,名为 hello.dispatch.h,可以被源树中的任何源文件访问,并且应包含以下代码:

代码语言:javascript复制
#ifndef NPY__CPU_DISPATCH_EXPAND_
  // To expand the macro calls in this header
  #define NPY__CPU_DISPATCH_EXPAND_(X) X
#endif
// Undefining the following macros, due to the possibility of including config headers
// multiple times within the same source and since each config header represents
// different required optimizations according to the specified configuration
// statements in the dispatch-able source that derived from it.
#undef NPY__CPU_DISPATCH_BASELINE_CALL
#undef NPY__CPU_DISPATCH_CALL
// nothing strange here, just a normal preprocessor callback
// enabled only if 'baseline' specified within the configuration statements
#define NPY__CPU_DISPATCH_BASELINE_CALL(CB, ...) 
 NPY__CPU_DISPATCH_EXPAND_(CB(__VA_ARGS__))
// 'NPY__CPU_DISPATCH_CALL' is an abstract macro is used for dispatching
// the required optimizations that specified within the configuration statements.
//
// @param CHK, Expected a macro that can be used to detect CPU features
// in runtime, which takes a CPU feature name without string quotes and
// returns the testing result in a shape of boolean value.
// NumPy already has macro called "NPY_CPU_HAVE", which fits this requirement.
//
// @param CB, a callback macro that expected to be called multiple times depending
// on the required optimizations, the callback should receive the following arguments:
//  1- The pending calls of @param CHK filled up with the required CPU features,
//     that need to be tested first in runtime before executing call belong to
//     the compiled object.
//  2- The required optimization name, same as in 'NPY__CPU_TARGET_CURRENT'
//  3- Extra arguments in the macro itself
//
// By default the callback calls are sorted depending on the highest interest
// unless the policy "$keep_sort" was in place within the configuration statements
// see "Dive into the CPU dispatcher" for more clarification.
#define NPY__CPU_DISPATCH_CALL(CHK, CB, ...) 
 NPY__CPU_DISPATCH_EXPAND_(CB((CHK(AVX512F)), AVX512F, __VA_ARGS__)) 
 NPY__CPU_DISPATCH_EXPAND_(CB((CHK(SSE)&&CHK(SSE2)&&CHK(SSE3)&&CHK(SSSE3)&&CHK(SSE41)), SSE41, __VA_ARGS__)) 

根据上述示例使用配置头文件的示例:

代码语言:javascript复制
// NOTE: The following macros are only defined for demonstration purposes only.
// NumPy already has a collections of macros located at
// numpy/numpy/core/src/common/npy_cpu_dispatch.h, that covers all dispatching
// and declarations scenarios.

#include  "numpy/npy_cpu_features.h" // NPY_CPU_HAVE
#include  "numpy/utils.h" // NPY_CAT, NPY_EXPAND

// An example for setting a macro that calls all the exported symbols at once
// after checking if they're supported by the running machine.
#define DISPATCH_CALL_ALL(FN, ARGS) 
 NPY__CPU_DISPATCH_CALL(NPY_CPU_HAVE, DISPATCH_CALL_ALL_CB, FN, ARGS) 
 NPY__CPU_DISPATCH_BASELINE_CALL(DISPATCH_CALL_BASELINE_ALL_CB, FN, ARGS)
// The preprocessor callbacks.
// The same suffixes as we define it in the dispatch-able source.
#define DISPATCH_CALL_ALL_CB(CHECK, TARGET_NAME, FN, ARGS) 
 if (CHECK) { NPY_CAT(NPY_CAT(FN, _), TARGET_NAME) ARGS; }
#define DISPATCH_CALL_BASELINE_ALL_CB(FN, ARGS) 
 FN NPY_EXPAND(ARGS);

// An example for setting a macro that calls the exported symbols of highest
// interest optimization, after checking if they're supported by the running machine.
#define DISPATCH_CALL_HIGH(FN, ARGS) 
 if (0) {} 
 NPY__CPU_DISPATCH_CALL(NPY_CPU_HAVE, DISPATCH_CALL_HIGH_CB, FN, ARGS) 
 NPY__CPU_DISPATCH_BASELINE_CALL(DISPATCH_CALL_BASELINE_HIGH_CB, FN, ARGS)
// The preprocessor callbacks
// The same suffixes as we define it in the dispatch-able source.
#define DISPATCH_CALL_HIGH_CB(CHECK, TARGET_NAME, FN, ARGS) 
 else if (CHECK) { NPY_CAT(NPY_CAT(FN, _), TARGET_NAME) ARGS; }
#define DISPATCH_CALL_BASELINE_HIGH_CB(FN, ARGS) 
 else { FN NPY_EXPAND(ARGS); }

// NumPy has a macro called 'NPY_CPU_DISPATCH_DECLARE' can be used
// for forward declarations any kind of prototypes based on
// 'NPY__CPU_DISPATCH_CALL' and 'NPY__CPU_DISPATCH_BASELINE_CALL'.
// However in this example, we just handle it manually.
void  simd_whoami(const  char  *extra_info);
void  simd_whoami_AVX512F(const  char  *extra_info);
void  simd_whoami_SSE41(const  char  *extra_info);

void  trigger_me(void)
{
  // bring the auto-generated config header
  // which contains config macros 'NPY__CPU_DISPATCH_CALL' and
  // 'NPY__CPU_DISPATCH_BASELINE_CALL'.
  // it is highly recommended to include the config header before executing
  // the dispatching macros in case if there's another header in the scope.
  #include  "hello.dispatch.h"
  DISPATCH_CALL_ALL(simd_whoami,  ("all"))
  DISPATCH_CALL_HIGH(simd_whoami,  ("the highest interest"))
  // An example of including multiple config headers in the same source
  // #include "hello2.dispatch.h"
  // DISPATCH_CALL_HIGH(another_function, ("the highest interest"))
} 

cessor callbacks. // The same suffixes as we define it in the dispatch-able source. #define DISPATCH_CALL_ALL_CB(CHECK, TARGET_NAME, FN, ARGS) if (CHECK) { NPY_CAT(NPY_CAT(FN, _), TARGET_NAME) ARGS; } #define DISPATCH_CALL_BASELINE_ALL_CB(FN, ARGS) FN NPY_EXPAND(ARGS);

代码语言:javascript复制
// An example for setting a macro that calls the exported symbols of highest
// interest optimization, after checking if they're supported by the running machine.
#define DISPATCH_CALL_HIGH(FN, ARGS) 
 if (0) {} 
 NPY__CPU_DISPATCH_CALL(NPY_CPU_HAVE, DISPATCH_CALL_HIGH_CB, FN, ARGS) 
 NPY__CPU_DISPATCH_BASELINE_CALL(DISPATCH_CALL_BASELINE_HIGH_CB, FN, ARGS)
// The preprocessor callbacks
// The same suffixes as we define it in the dispatch-able source.
#define DISPATCH_CALL_HIGH_CB(CHECK, TARGET_NAME, FN, ARGS) 
 else if (CHECK) { NPY_CAT(NPY_CAT(FN, _), TARGET_NAME) ARGS; }
#define DISPATCH_CALL_BASELINE_HIGH_CB(FN, ARGS) 
 else { FN NPY_EXPAND(ARGS); }

// NumPy has a macro called 'NPY_CPU_DISPATCH_DECLARE' can be used
// for forward declarations any kind of prototypes based on
// 'NPY__CPU_DISPATCH_CALL' and 'NPY__CPU_DISPATCH_BASELINE_CALL'.
// However in this example, we just handle it manually.
void  simd_whoami(const  char  *extra_info);
void  simd_whoami_AVX512F(const  char  *extra_info);
void  simd_whoami_SSE41(const  char  *extra_info);

void  trigger_me(void)
{
  // bring the auto-generated config header
  // which contains config macros 'NPY__CPU_DISPATCH_CALL' and
  // 'NPY__CPU_DISPATCH_BASELINE_CALL'.
  // it is highly recommended to include the config header before executing
  // the dispatching macros in case if there's another header in the scope.
  #include  "hello.dispatch.h"
  DISPATCH_CALL_ALL(simd_whoami,  ("all"))
  DISPATCH_CALL_HIGH(simd_whoami,  ("the highest interest"))
  // An example of including multiple config headers in the same source
  // #include "hello2.dispatch.h"
  // DISPATCH_CALL_HIGH(another_function, ("the highest interest"))
} 
```

0 人点赞