有时会发生意想不到的事情,必须与世界分享......这就是这样的情况。
最近,我开始尝试使用 perl 进行数据科学应用程序的工作流管理和低级代码的高级监督。在这种情况下,我为 perl 保留的一个角色是内存缓冲区的生命周期管理,使用 perl 应用程序“分配”内存缓冲区并在用 c、assembly、fortran 编写的计算组件和最好的隐藏宝石之间穿梭。 perl 世界,perl 数据语言。
perl 至少可以通过 3 种方式来分配内存缓冲区:
以下 perl 代码实现了这三种方法(pack、string 和 c 中的 malloc),并允许尝试不同的缓冲区大小、初始值和结果精度(通过对分配的多次迭代进行平均)惯例)
#!/home/chrisarg/perl5/perlbrew/perls/current/bin/perl use v5.38; use inline ( c => 'data', cc => 'g++', ld => 'g++', inc => q{}, # replace q{} with anything else you need ccflagsex => q{}, # replace q{} with anything else you need lddlflags => join( q{ }, $config::config{lddlflags}, q{ }, # replace q{ } with anything else you need ), libs => join( q{ }, $config::config{libs}, q{ }, # replace q{ } with anything else you need ), myextlib => '' ); use benchmark qw(cmpthese); use getopt::long; my ($buffer_size, $init_value, $iterations); getoptions( 'buffer_size=i' => $buffer_size, 'init_value=s' => $init_value, 'iterations=i' => $iterations, ) or die "usage: $0 --buffer_size <size> --init_value <value> --iterations <count>n"; my $init_value_byte = ord($init_value); my %code_snippets = ( 'string' => sub { $init_value x ( $buffer_size - 1 ); }, 'pack' => sub { pack "c*", ( ($init_value_byte) x $buffer_size ); }, 'c' => sub { allocate_and_initialize_array( $buffer_size, $init_value_byte ); }, ); cmpthese( $iterations, %code_snippets ); __data__ __c__ #include <stdio.h> #include <stdlib.h> #include <stdint.h> sv* allocate_and_initialize_array(size_t length, short initial_value) { // allocate memory for the array char* array = (char*)malloc(length * sizeof(char)); char initial_value_byte = (char)initial_value; if (array == null) { fprintf(stderr, "memory allocation failedn"); exit(1); } // initialize each element with the initial_value memset(array, initial_value_byte, length); return newsvuv(ptr2uv(array)); }
将脚本调用为:
./time_mem_alloc.pl -buffer_size=1000000 -init_value=a -iterations=20000
产生了令人惊讶的结果:
rate pack c string pack 322/s -- -92% -99% c 4008/s 1144% -- -92% string 50000/s 15417% 1147% --
使用 perl string 方法比 c 的性能高出 10 倍。
不相信巨大的性能提升,并认为我正在处理 inline::c 中的错误,我用纯 c 重新编码了分配(添加命令行处理/计时等的常用修饰):
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> char* allocate_and_initialize_array(size_t length, char initial_value) { // allocate memory for the array char* array = (char*)malloc(length * sizeof(char)); if (array == null) { fprintf(stderr, "memory allocation failedn"); exit(1); } // initialize each element with the initial_value memset(array, initial_value, length); return array; } double time_allocation_and_initialization(size_t length, char initial_value) { clock_t start, end; double cpu_time_used; start = clock(); char* array = allocate_and_initialize_array(length, initial_value); end = clock(); cpu_time_used = ((double) (end - start)) / clocks_per_sec; /* this rudimentary loop prevents the compiler from optimizing out the * allocation/initialization with the de-allocation */ for(size_t i = 1; i < length; i++) { array[i]++; if(i % 100000 == 0) { printf("array[%zu] = %cn", i, array[i]); } } free(array); // free the allocated memory return cpu_time_used; } int main(int argc, char *argv[]) { if (argc != 3) { fprintf(stderr, "usage: %s <length> <initial_value>n", argv[0]); return 1; } size_t length = strtoull(argv[1], null, 10); char initial_value = argv[2][0]; double time_taken = time_allocation_and_initialization(length, initial_value); printf("time taken to allocate and initialize array: %f secondsn", time_taken); printf("initializes per second: %fn", 1/time_taken); return 0; } /* compilation command: gcc -o2 -o time_array_allocation time_array_allocation.c -std=c99 example invocation: ./time_array_allocation 10000000 a */
按照c代码中注释所说的那样调用c程序,
我得到了以下结果:
Time taken to allocate and initialize array: 0.000203 seconds Initializes per second: 4926.108374
实际上执行的数量级与 inline::c malloc/c 方法的等效分配相同。
在进一步研究这个问题后,我发现我所欣赏的 malloc 牺牲了内存分配的速度以换取通用性,并且有大量更快的内存分配器。看来 perl 正在为其字符串使用这样一个分配器,并在分配缓冲区的任务中击败了 c。