有时会发生意想不到的事情,必须与世界分享......这就是这样的情况。
最近,我开始尝试使用 perl 进行数据科学应用程序的工作流管理和低级代码的高级监督。在这种情况下,我为 perl 保留的一个角色是内存缓冲区的生命周期管理,使用 perl 应用程序“分配”内存缓冲区并在用 c、assembly、fortran 编写的计算组件和最好的隐藏宝石之间穿梭。 perl 世界,perl 数据语言。
perl 至少可以通过 3 种方式来分配内存缓冲区:
- 生成字节列表并使用 pack 函数将它们转换为字符串。
- 使用重复运算符 (x) 生成一个长度等于所需缓冲区大小减一的字符串(在 perl 中,字符串通过空字节终止,因此空字节补偿了我的一个 ).
- 通过 inline 或 ffi::platypus 访问外部内存分配器库来分配缓冲区。
以下 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));
}
</stdint.h></stdlib.h></stdio.h></count></value></size>
将脚本调用为:
./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 <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
*/
</initial_value></time.h></string.h></stdlib.h></stdio.h>
按照c代码中注释所说的那样调用c程序,
我得到了以下结果:
Time taken to allocate and initialize array: 0.000203 seconds
Initializes per second: 4926.108374
实际上执行的数量级与 inline::c malloc/c 方法的等效分配相同。
在进一步研究这个问题后,我发现我所欣赏的 malloc 牺牲了内存分配的速度以换取通用性,并且有大量更快的内存分配器。看来 perl 正在为其字符串使用这样一个分配器,并在分配缓冲区的任务中击败了 c。