用C ++初始化非常大的向量

时间:2016-09-16 10:57:34

标签: python c++ arrays

我在python中创建了非常大的O(10M)浮点列表。我想在我的C ++项目中使用这个查找表。将此数组从python转移到C ++的最简单,最有效的方法是什么?

我的第一个想法是生成c ++函数,它负责初始化这样的长向量然后编译它。 python代码如上所示:

TextItem

输出文件的大小为500 MB。并且无法编译它(由于gcc崩溃而导致编译终止):

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

另一个想法是将python数组存储到二进制文件中,然后用C ++读取它。但这很棘手。我无法正确阅读它。 我使用这么简单的命令生成表:

../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.

你能告诉我怎么办?我用Google搜索了,我无法找到足够的答案。

你知道我怎么能处理这么大的阵列。

我应该给你更详细的问题描述。 我使用python训练ML(sklearn)分类器,然后我想用C ++部署它。 Doe到计时问题(执行速度是我研究的一个关键部分)我使用bonsai boosted decision trees的想法。在这种方法中,您将BDT转移到查找表中。

3 个答案:

答案 0 :(得分:4)

如果您正在使用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中。

答案 1 :(得分: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所示相同的输出。

答案 2 :(得分:0)

正如您所注意到的,编译器在这样的大数据阵列上崩溃。

除了读取二进制文件(因为您不想这样做)之外,您可以做的是链接汇编文件。它仍然使可执行文件自给自足,并且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兆字节的二进制文件(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

此方法的主要优点是您可以使用所需的格式定义任意数量的符号。