【问题标题】:What is numpy.ctypeslib.as_ctypes exacty doingnumpy.ctypeslib.as_ctypes 到底在做什么
【发布时间】:2021-07-09 09:45:13
【问题描述】:

我有这个 Fortran 代码:

module example

    use iso_c_binding
    implicit none
    
contains

    subroutine array_by_ref_modifying(array, nbrows, nbcols, coeff) BIND(C, NAME='array_by_ref_modifying')
        !DEC$ ATTRIBUTES DLLEXPORT :: array_by_ref_modifying
        integer, intent(in) :: nbrows, nbcols, coeff
        integer, intent(inout) :: array(nbrows, nbcols)
        integer :: i, j
        do j = 1, nbcols
            do i = 1, nbrows
                array(i, j) = coeff * array(i, j)
            enddo
        enddo
    end subroutine array_by_ref_modifying

end module example

比我编译成一个我从 Python 调用的 TestLib.dll 如下:

redist_path = r"C:\Program Files (x86)\Intel\oneAPI\compiler\2021.3.0\windows\redist\intel64_win\compiler"
dll_full_name = r"C:\DLLS\TestLib.dll"

import os
os.add_dll_directory(redist_path)

import ctypes as ct
import numpy as np

fortlib = ct.CDLL(dll_full_name)

nbrows = 2
nbcols = 6
pnbrows = ct.pointer( ct.c_int(nbrows))  
pnbcols = ct.pointer( ct.c_int(nbcols))
myarr = np.array([[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]], dtype=ct.c_int)

print(myarr)

coeff = 2
pcoeff = ct.pointer( ct.c_int(coeff))

pyfunc_array_by_ref_modifying = getattr(fortlib, "array_by_ref_modifying")
pyfunc_array_by_ref_modifying(np.ctypeslib.as_ctypes(myarr), pnbrows, pnbcols, pcoeff)

print(myarr)

python 代码输出:

[[ 1  2  3  4  5  6]
 [ 7  8  9 10 11 12]]
 [[ 2  4  6  8 10 12]
 [14 16 18 20 22 24]]

正如预期的那样。现在,我期望更少的是,如果我将 python 脚本替换为“自然”位

nbrows = 2
nbcols = 6

(在 Fortran 中引导到 (1:2,1:6) 数组)带有“不太自然”的位

nbrows = 6
nbcols = 2

(在 Fortran 中引导到 (1:6,1:2) 数组,从而表明将正确的数组传递给 Fortran)python 脚本仍然输出

[[ 1  2  3  4  5  6]
 [ 7  8  9 10 11 12]]
 [[ 2  4  6  8 10 12]
 [14 16 18 20 22 24]]

据我了解,在行

pyfunc_array_by_ref_modifying(np.ctypeslib.as_ctypes(myarr), pnbrows, pnbcols, pcoeff)

np.ctypeslib.as_ctypes(myarr) 做了一些预处理/转置。真的是这样吗?

我不希望进行这种预处理,因为它的性能开销在具有大数组维度的实际代码中可能很重要。

(我猜“正确”的方式是使用“自然位”,这并不意味着任何预处理。)

【问题讨论】:

  • 不确定是否相关:如果设置函数的 argtypesrestype 会发生什么?无论如何,我的猜测是您在这里有一个 Undefined Behavior,因为数组是 2 X 6在其创建时建立),并且通过切换您可能会遇到麻烦的尺寸(考虑到内部 1D 数组对齐),在您的情况下没有发生的事情,Fortran 做了正确的乘法(在“转置”数组上),然后你得到相乘的数组。
  • 对不起,我好像遗漏了什么。究竟是什么问题?什么不能正常工作?
  • 问题是在 2 和 6 的切换下,一切都可以工作,而只有 2 行 6 列的情况才可以工作。
  • 您能否编辑问题,添加您认为在切换维度顺序时应该输出的内容?并解释(几句话)为什么?
  • 嗯,我不清楚出了什么问题。问题中唯一表明 smth 错误的是“python 脚本仍然输出”,然后结合之前的评论之一,它不应该像切换行/列。这就是为什么我要问你认为它在第二种情况下应该输出什么。

标签: python numpy fortran ctypes intel-fortran


【解决方案1】:

由于我仍然不明白问题是什么,我只是假设,并尝试“盲目地”回答。

我将首先回答准时的问题(在结尾处):
numpy.ctypeslib.as_ctypes 转换 NumPy 数组(或对象,或...) 变成一个CTypes。但是转换只发生在元数据上(因为 2 个模块不同)。 数组内容(或指针,或实际数组数据所在(或开始)的内存地址)保持不变复制/修改/更改,...)。

参考资料:

  1. [NumPy.Docs]: C-Types Foreign Function Interface (numpy.ctypeslib)

    1.1。源代码(末尾某处):[GitHub]: numpy/numpy - numpy/numpy/ctypeslib.py

  2. [Python.Docs]: ctypes - A foreign function library for Python

因此,没有进行转置。
我不确定您所说的“预处理”是什么意思(as_ctypes 执行的所有检查和操作都适合吗?)。 “natural bit”也一样。

还要注意 as_ctypespyfunc_array_by_ref_modifying 的 1st 参数)完全不知道其余的(pnbrows em> 和 pnbcols 特别是)。

即使没有直接影响(我认为这是“运气”的问题),您可能想看看以下内容:[SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer)

去(我认为是)真正的问题(导致问题):

  • 数组通过reference(其缓冲区起始内存地址)传递给Fortran 子例程。我没有官方消息来源来说明这一点(从一开始就是一个假设),但这就像在 C 中传递一个指针(我猜它与 C绑定来自子程序声明)
  • 没有传递元数据(行、列)(否则接下来的 2 个参数将无用)

(2D) 数组作为 1D 之一存储在内存中:第 1st 行,然后是第 2nd 一个,第三个rd,...,最后一个。

为了到达元素array[i][j] (i = [0, row_count), j = [0, column_count)),使用如下公式(指针逻辑):
array_start_address + array_element_size_in_bytes * (i * column_count + j)

希望消除一些困惑,这里有一个小的 C 演示。我复制了(我认为的)Fortran 子程序的行为。我还增加了行数以使事情更清晰。

main00.c

#include <stdio.h>
#include <string.h>

#define ROWS 3
#define COLS 6
//#pragma align 4


typedef int Array2D[ROWS][COLS];

/*
void modifyArray(Array2D array, int rows, int cols, int coef) {
    for (int i = 0; i < rows; ++i)
        for (int j = 0; j < cols; ++j)
            array[i][j] *= coef;
}
//*/

void modifyPtr(int *pArray, int rows, int cols, int coef) {  // This is the equivalent.
    for (int i = 0; i < rows; ++i)
        for (int j = 0; j < cols; ++j)
            (*(pArray + (i * cols + j))) *= coef;
}

void printArray(Array2D array, int rows, int cols, char *head) {
    printf("\n%s:\n", head);
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j)
            printf("% 3d  ", array[i][j]);
        printf("\n");
    }
}

void modify(Array2D arr, int rows, int cols, int coef, char *head) {
    printf("\nRows: %d, Cols: %d, Coef: %d", rows, cols, coef);
    Array2D arr_dup;
    memcpy(arr_dup, arr, sizeof(Array2D));
    modifyPtr(arr_dup, rows, cols, coef);
    printArray(arr_dup, ROWS, COLS, head);
}

int main() {
    Array2D arr = {  // ROWS X COLS
        { 1, 2, 3, 4, 5, 6 },
        { 7, 8, 9, 10, 11, 12 },
        { 13, 14, 15, 16, 17, 18 },
    };
    char *modifiedText = "Modified";

    //printf("Array size: %d %d %d\n", sizeof(arr), sizeof(arr[0]), sizeof(arr[0][0]));

    printArray(arr, ROWS, COLS, "Original");

    modify(arr, 3, 6, 2, modifiedText);
    printArray(arr, ROWS, COLS, "Original");
    modify(arr, 2, 6, 2, modifiedText);
    modify(arr, 1, 6, 2, modifiedText);
    modify(arr, 3, 5, 2, modifiedText);
    modify(arr, 3, 4, 2, modifiedText);
    modify(arr, 5, 2, 2, modifiedText);
    modify(arr, 7, 1, 2, modifiedText);

    printf("\nDone.\n");
    return 0;
}

输出

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q068314707]> sopr.bat
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity\2019\VC\Auxiliary\Build\vcvarsall.bat" x64 > nul

[prompt]> dir /b
code00.py
example.f90
main00.c

[prompt]>
[prompt]> cl /nologo /MD /W0 main00.c  /link /NOLOGO /OUT:main00_pc064.exe
main00.c

[prompt]> dir /b
code00.py
example.f90
main00.c
main00.obj
main00_pc064.exe

[prompt]>
[prompt]> main00_pc064.exe

Original:
  1    2    3    4    5    6
  7    8    9   10   11   12
 13   14   15   16   17   18

Rows: 3, Cols: 6, Coef: 2
Modified:
  2    4    6    8   10   12
 14   16   18   20   22   24
 26   28   30   32   34   36

Original:
  1    2    3    4    5    6
  7    8    9   10   11   12
 13   14   15   16   17   18

Rows: 2, Cols: 6, Coef: 2
Modified:
  2    4    6    8   10   12
 14   16   18   20   22   24
 13   14   15   16   17   18

Rows: 1, Cols: 6, Coef: 2
Modified:
  2    4    6    8   10   12
  7    8    9   10   11   12
 13   14   15   16   17   18

Rows: 3, Cols: 5, Coef: 2
Modified:
  2    4    6    8   10   12
 14   16   18   20   22   24
 26   28   30   16   17   18

Rows: 3, Cols: 4, Coef: 2
Modified:
  2    4    6    8   10   12
 14   16   18   20   22   24
 13   14   15   16   17   18

Rows: 5, Cols: 2, Coef: 2
Modified:
  2    4    6    8   10   12
 14   16   18   20   11   12
 13   14   15   16   17   18

Rows: 7, Cols: 1, Coef: 2
Modified:
  2    4    6    8   10   12
 14    8    9   10   11   12
 13   14   15   16   17   18
Done.

返回 Fortran(在本地保存您的脚本)+ Python(创建一个新脚本)。

code00.py

#!/usr/bin/env python

import sys
import ctypes as ct
import numpy as np


IntPtr = ct.POINTER(ct.c_int)

DLL_NAME = "./example.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")


def modify(arr, rows, cols, coef, modify_func):
    print("\nRows: {:d}, Cols {:d}, Coef: {:d}".format(rows, cols, coef))
    arr_dup = arr.copy()
    arr_ct = np.ctypeslib.as_ctypes(arr_dup)
    rows_ct = ct.c_int(rows)
    cols_ct = ct.c_int(cols)
    coef_ct = ct.c_int(coef)
    modify_func(arr_ct, ct.byref(rows_ct), ct.byref(cols_ct), ct.byref(coef_ct))
    print("Modified array:\n {:}".format(arr_dup))


def main(*argv):
    dll00 = ct.CDLL(DLL_NAME)
    func = getattr(dll00, "array_by_ref_modifying")
    #func.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p)
    func.argtypes = (ct.c_void_p, IntPtr, IntPtr, IntPtr)
    func.restype = None

    arr = np.array([
        [1, 2, 3, 4, 5, 6],
        [7, 8, 9, 10, 11, 12],
        [13, 14, 15, 16, 17, 18],
    ], dtype=ct.c_int)

    print("Original array:\n {:}".format(arr))

    modify(arr, 3, 6 , 2, func)
    modify(arr, 2, 6 , 2, func)
    modify(arr, 1, 6 , 2, func)
    modify(arr, 3, 5 , 2, func)
    modify(arr, 3, 4 , 2, func)
    modify(arr, 5, 2 , 2, func)
    modify(arr, 7, 1 , 2, func)


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

输出

[prompt]> "f:\Install\pc032\Intel\OneAPI\Version\compiler\2021.3.0\windows\bin\intel64\ifort.exe" /c example.f90
Intel(R) Fortran Intel(R) 64 Compiler Classic for applications running on Intel(R) 64, Version 2021.3.0 Build 20210609_000000
Copyright (C) 1985-2021 Intel Corporation.  All rights reserved.


[prompt]> link /NOLOGO /DLL /OUT:example.dll /LIBPATH:"f:\Install\pc032\Intel\OneAPI\Version\compiler\2021.3.0\windows\compiler\lib\intel64_win" example.obj
   Creating library example.lib and object example.exp

[prompt]> dir /b
code00.py
example.dll
example.exp
example.f90
example.lib
example.mod
example.obj
main00.c
main00.obj
main00_pc064.exe

[prompt]>
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.08.07_test0\Scripts\python.exe" code00.py
Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)] 064bit on win32

Original array:
 [[ 1  2  3  4  5  6]
 [ 7  8  9 10 11 12]
 [13 14 15 16 17 18]]

Rows: 3, Cols 6, Coef: 2
Modified array:
 [[ 2  4  6  8 10 12]
 [14 16 18 20 22 24]
 [26 28 30 32 34 36]]

Rows: 2, Cols 6, Coef: 2
Modified array:
 [[ 2  4  6  8 10 12]
 [14 16 18 20 22 24]
 [13 14 15 16 17 18]]

Rows: 1, Cols 6, Coef: 2
Modified array:
 [[ 2  4  6  8 10 12]
 [ 7  8  9 10 11 12]
 [13 14 15 16 17 18]]

Rows: 3, Cols 5, Coef: 2
Modified array:
 [[ 2  4  6  8 10 12]
 [14 16 18 20 22 24]
 [26 28 30 16 17 18]]

Rows: 3, Cols 4, Coef: 2
Modified array:
 [[ 2  4  6  8 10 12]
 [14 16 18 20 22 24]
 [13 14 15 16 17 18]]

Rows: 5, Cols 2, Coef: 2
Modified array:
 [[ 2  4  6  8 10 12]
 [14 16 18 20 11 12]
 [13 14 15 16 17 18]]

Rows: 7, Cols 1, Coef: 2
Modified array:
 [[ 2  4  6  8 10 12]
 [14  8  9 10 11 12]
 [13 14 15 16 17 18]]

Done.

结论

  • 在 2 个示例中以相同的方式修改数组,证实了我的假设
  • 无论传递什么行号和列号,数组都是从头修改,从左到右,从上到下。这可能有点令人困惑(当描绘 2D 表示并例如传递一个小于实际列号的列号时)。这就是为什么它使用比实际尺寸更大的尺寸。唯一需要注意的是不要超过元素的数量(row_count * column_count),因为这会产生未定义的行为(它可能会崩溃)
  • 我不确定这一点,但还是要提一下:可能还有其他一些未定义行为情况,例如数组中的每一行都将被填充由编译器正确对齐(类似于#pragma pack)。示例:具有 7 列(一行将有 7 个字节)的 char 数组可能是 8 个字节对齐的。不确定指针逻辑如何处理这个问题

【讨论】:

    猜你喜欢
    • 2011-10-30
    • 2010-12-30
    • 2014-03-20
    • 2019-06-14
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    • 2022-06-15
    相关资源
    最近更新 更多