在 C++ 中初始化非常大的向量
Posted
技术标签:
【中文标题】在 C++ 中初始化非常大的向量【英文标题】:Initialization of very big vector in C++ 【发布时间】:2016-09-16 10:57:33 【问题描述】:我在 python 中创建了非常大的 O(10M) 浮点列表。我想在我的 C++ 项目中使用这个查找表。将此数组从 python 传输到 C++ 的最简单和最有效的方法是什么。
我的第一个想法是生成c++函数,负责对这么长的向量进行初始化,然后编译。 python代码如上所示:
def generate_initLookupTable_function():
numbers_per_row = 100
function_body = """
#include "PatBBDTSeedClassifier.h"
std::vector<double> PatBBDTSeedClassifier::initLookupTable()
std::vector<double> indicesVector =
"""
row_nb = 1
for bin_value in classifier._lookup_table[:,0]:
function_body += "\t" + str(bin_value) +" , "
if (row_nb % numbers_per_row) == 0:
function_body += "\n"
row_nb += 1
function_body += """\n ;
return indicesVector;
"""
return function_body
输出文件的大小为 500 MB。并且无法编译(编译因 gcc 崩溃而终止):
../src/PatBBDTSeedClassifier_lookupTable.cpp
lcg-g++-4.9.3: internal compiler error: Killed (program cc1plus)
0x409edc execute
../../gcc-4.9.3/gcc/gcc.c:2854
Please submit a full bug report,
with preprocessed source if appropriate.
Please include the complete backtrace with any bug report.
另一个想法是将python数组存储到二进制文件中,然后用C++读取。但这很棘手。我无法正确阅读它。 我使用这样简单的命令生成表:
file = open("models/BBDT_lookuptable.dat", 'wb')
table = numpy.array(classifier._lookup_table[:,0])
table.tofile(file)
file.close()
你能告诉我我该怎么做吗?我用谷歌搜索,我找不到足够的答案。
你知道我该如何处理这么大的数组吗?
我应该给你更详细的问题描述。 我使用 python 来训练 ML (sklearn) 分类器,然后我想在 C++ 中部署它。 Doe to time 问题(执行速度是我研究的关键部分)我使用bonsai boosted decision trees 的想法。在这种方法中,您将 BDT 转移到查找表中。
【问题讨论】:
您是否有其他进程需要使用来自 Python 进程的数据?还是 C++“程序”应该是 Python 中使用的模块?您能否详细说明您试图通过“传输”数据来解决的潜在问题? 您能透露一些生成的 C++ 代码以便更好地理解问题吗? “另一个想法是将python数组存储到二进制文件中,然后用C++读取它。但这很棘手。我无法正确读取它。”。不幸的是,这是您唯一的声音选项 :) 等等,不:您可以生成 asm 而不是 C 并将 asm 代码链接到您的 C 代码。 出于好奇:你为什么不把它放在一个大的压缩二进制文件中呢?一定有一些很好的理由,因为它可以为您省去麻烦并且可能加载更快(试试Zstd)。 问题是我不知道如何从这样的文件中抓取表格。 【参考方案1】:如果您使用的是 GNU 工具,直接使用 objcopy
来实现 Jean-Francois 所建议的功能相当容易;结合写二进制数组的PM2Ring的python脚本,可以执行:
objcopy -I binary test.data -B i386:x86-64 -O elf64-x86-64 testdata.o
(根据您的实际处理器架构,您可能需要进行调整)。该命令将使用以下符号创建一个名为 testdata.o
的新对象:
0000000000000100 D _binary_test_data_end
0000000000000100 A _binary_test_data_size
0000000000000000 D _binary_test_data_start
所有这些符号在链接程序中都将作为带有 C 链接的符号可见。 size
本身不能使用(它也将转换为地址),但可以使用 *start
和 *end
。这是一个最小的 C++ 程序:
#include <iostream>
extern "C" double _binary_test_data_start[];
extern "C" double _binary_test_data_end[0];
int main(void)
double *d = _binary_test_data_start;
const double *end = _binary_test_data_end;
std::cout << (end - d) << " doubles in total" << std::endl;
while (d < end)
std::cout << *d++ << std::endl;
_binary_test_data_end
实际上将刚好超过数组 _binary_test_data_start
中的最后一个元素。
编译 + 将此程序与 g++ test.cc testdata.o -o program
链接(使用上面 objcopy 中的 testdata.o)。
输出(cout
默认情况下似乎会尴尬地截断小数):
% ./a.out
32 doubles in total
0
0.0625
0.125
0.1875
0.25
0.3125
0.375
0.4375
0.5
0.5625
0.625
0.6875
0.75
0.8125
0.875
0.9375
1
1.0625
1.125
1.1875
1.25
1.3125
1.375
1.4375
1.5
1.5625
1.625
1.6875
1.75
1.8125
1.875
1.9375
你也可以很容易地将这些值分配到一个向量中; std::vector<double>
接受 2 个迭代器,其中第一个指向第一个元素,第二个指向后面的一个;您可以在这里使用数组,因为它们会衰减为指针,并且指针可以用作迭代器:
std::vector<double> vec(_binary_test_data_start, _binary_test_data_end);
但是,对于大数组,这只是不必要的复制。此外,仅使用 C 数组还有一个额外的好处,那就是它延迟加载; ELF 可执行文件不会读入内存,但会根据需要进行分页;二进制数组仅在被访问时才从文件加载到 RAM 中。
【讨论】:
【参考方案2】:这是一个简单的示例,说明如何将 Python 浮点数据写入二进制文件,以及如何在 C 中读取该数据。为了对数据进行编码,我们使用了struct
模块。
savefloat.py
>#!/usr/bin/env python3
from struct import pack
# The float data to save
table = [i / 16.0 for i in range(32)]
# Dump the table to stdout
for i, v in enumerate(table):
print('%d: %f' % (i, v))
# Save the data to a binary file
fname = 'test.data'
with open(fname, 'wb') as f:
for u in table:
# Pack doubles as little-endian
f.write(pack(b'<d', u))
输出
0: 0.000000
1: 0.062500
2: 0.125000
3: 0.187500
4: 0.250000
5: 0.312500
6: 0.375000
7: 0.437500
8: 0.500000
9: 0.562500
10: 0.625000
11: 0.687500
12: 0.750000
13: 0.812500
14: 0.875000
15: 0.937500
16: 1.000000
17: 1.062500
18: 1.125000
19: 1.187500
20: 1.250000
21: 1.312500
22: 1.375000
23: 1.437500
24: 1.500000
25: 1.562500
26: 1.625000
27: 1.687500
28: 1.750000
29: 1.812500
30: 1.875000
31: 1.937500
loadfloat.c
/* Read floats from a binary file & dump to stdout */
#include <stdlib.h>
#include <stdio.h>
#define FILENAME "test.data"
#define DATALEN 32
int main(void)
FILE *infile;
double data[DATALEN];
int i, n;
if(!(infile = fopen(FILENAME, "rb")))
exit(EXIT_FAILURE);
n = fread(data, sizeof(double), DATALEN, infile);
fclose(infile);
for(i=0; i<n; i++)
printf("%d: %f\n", i, data[i]);
return 0;
上面的 C 代码产生与 savefloat.py
相同的输出。
【讨论】:
谢谢!我一直在尝试用 C++ 实现这个功能。但正如你所展示的,C 更容易。 @user1877600:不用担心。我确信在 C++ 中读取浮点数据也很简单,如果您真的需要它,但我从未学过 C++。通过以块(例如 64 kB)写入数据可以使 Python 代码更快,但这可能不是必需的,因为现代操作系统在缓冲磁盘操作方面非常有效。 @user1877600:我刚刚修改了 C 代码,使其更加健壮。 对此的 C++ 解决方案与 C 解决方案几乎相同,有一种方法istream::read
与 fread
非常相似。【参考方案3】:
如您所见,编译器在此类大数据数组上崩溃。
除了读取二进制文件(因为您不想这样做)之外,您还可以做的是与汇编文件链接。它仍然使可执行文件自给自足,并且 GAS 对大文件的容忍度更高。这是我使用 python 生成的一些 asm 文件的示例,它与经典的 gcc
组装得很好:
.section .rodata
.globl FT
.globl FT_end
FT:
.byte 0x46,0x54,0x5f,0x43,0x46,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0x43,0x4f,0x4d,0x50
.byte 0x32,0x30,0x31,0x0,0x3,0x88,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0
.byte 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0
.byte 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x28,0xe6,0x47,0x6,0x7,0x8,0x28,0x28
.byte 0x26,0x6,0x2a,0x6,0x6,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0
FT_end:
该技术允许我在可执行文件中嵌入一个 80 MB 的二进制文件(100 万行代码),因为我没有文件系统来读取该环境中的数据文件 (QEMU)
用我终于可以挖掘出来的python代码进行实际测试:
Python 代码:
floats = [0.12,0.45,0.34,4.567,22.7]
import struct
contents = struct.pack('f'*len(floats), *floats)
outbase = "_extref"
output_file = "data.s"
fw = open(output_file,"w")
fw.write(""".section .rodata
.globl 0
.globl 0_end
0:
""".format(outbase,outbase))
eof = False
current_offset = 0
while not eof:
to_write = []
if current_offset==len(contents):
break
if current_offset<len(contents):
fw.write(".byte ")
for i in range(0,16):
if current_offset<len(contents):
to_write.append(hex(ord(contents[current_offset])))
current_offset+=1
else:
eof = True
break
if len(to_write)>0:
fw.write(",".join(to_write)+"\n")
fw.write(outbase+"_end:\n")
fw.close()
test.cpp
:C++ 代码(C++11,我为 asm 部分的指针引用而苦苦挣扎):
#include <iostream>
#include <vector>
#include <strings.h>
extern const float extref;
extern const float extref_end;
int main()
int size = (&extref_end - &extref);
std::cout << "nb_elements: " << size << std::endl;
std::vector<float> v(size);
memcpy(&v[0],&extref,sizeof(float)*size);
for (auto it : v)
std::cout << it << std::endl;
return 0;
Python 代码生成一个data.s
文件。创建一个可执行文件:
g++ -std=c++11 test.cpp data.s
运行:
nb_elements: 5
0.12
0.45
0.34
4.567
22.7
此方法的主要优点是您可以使用所需的格式定义任意数量的符号。
【讨论】:
以上是关于在 C++ 中初始化非常大的向量的主要内容,如果未能解决你的问题,请参考以下文章