encoder
「探秘」Jerasure那些事 - Durant Thorvalds 的米奇妙妙屋
// inputfile k m tech w p_size b_size
./encoder "/home/han/data/Jerasure/demo_data/pic.jpg" 4 2 "reed_sol_van" 8 8 1024
参数
(argv[0]是指向可执行文件的文件名,argv[1-8]是参数,argc是8)
- inputfile:需要被编码的文件。
- k:将文件划分的数据块的数量
- m:根据数据块生成的校验块的数量
- coding_technique:
- 使用的编码算法。可选的有"reed_sol_van", “reed_sol_r6_op”, “cauchy_orig”, “cauchy_good”, “liberation”, “blaum_roth”, “liber8tion”, “no_coding”,在这里我们使用"reed_sol_van"
- w:bit-word size。
- 范得蒙德算法的w只能从{8, 16, 32}中选取,这里我们选择8
- packetsize:
- 以packetsize bytes大小的包为单位进行编码, packetsize个word。决定了packet的大小这里我们选择8
- buffer size:
- 每次处理的数据大小,这里我们选择1024 .buffersize是源数据在编码分片时设置的缓冲区大小,设置缓冲区的目的是当用户编码的文件过大,读入内存的数据过多会导致编码效率下降,所以选择合适大小的分段缓存可以让编码效率最高。
- 另一个作用是用户节点在下载文件时解码也要用相同的大小buffersize才能正确聚合文件,在一定程度上保护了用户数据的隐私性。这个数值在编码运行时会自动调整,调整到适当的大小,目的是更快地(以最少次数readins)将文件读入内存。
处理参数
对buffersize向上取整
-
buffersize%(sizeof(long)*w*k*packetsize) != 0) // buffersize应该是 8*w*k*packetsize的整数倍 ----- 为什么??? // 在这里,我的buffersize被向上取整到 2048 han@han-virtual-machine:~/data/Jerasure/Examples$ ./encoder "/home/han/data/Jerasure/demo_data/pic.jpg" 4 2 "reed_sol_van" 8 8 1024 00 >> buffersize = 1024 sizeof(long)*w*k*packetsize = 8 * 8 * 4 * 8 = 2048 11 >> buffersize = 2048 >> blocksize = 512 22 >> buffersize = 2048
确定size大小
stat(argv[1], &status);
size = status.st_size; // 确定 size的大小 - 文件的size
确定newsize
newsize取整后的size, 便于分割成k个block
// newsize 需要是 k*w *packetsize *8 的整数倍
while (newsize%(k*w*packetsize*sizeof(long)) != 0)
newsize++;
// 同时 newsize需要是 buffersize的整数倍
if (buffersize != 0) {
while (newsize%buffersize != 0) {
newsize++;
}
}
确定blocksize
// blocksize 是对于文件分片之后的大小
// 是一次读入buffer后的处理单位 - block
blocksize = newsize/k;
考虑到size小于buffersize和size大于buffersize的情况
if (size > buffersize && buffersize != 0) { // size大于 buffersize的情况
// 确定readins的大小 - 即从外存读入内存的次数
// 需要分多次将file读入到data中
if (newsize%buffersize != 0) {
readins = newsize/buffersize;
}
else {
readins = newsize/buffersize;
}
// 这里的block是 k个block的合集而不是单个block,严谨来说,应该是 blocks
block = (char *)malloc(sizeof(char)*buffersize);
blocksize = buffersize/k;
}
else { // size小于buffersize的情况 - buffer收缩
readins = 1;
buffersize = size; // buffersize收缩到size大小
block = (char *)malloc(sizeof(char)*newsize);
}
编码过程
// 一次编码过程 是以 buffersize为单位对源文件进行划分
// 一次读入buffersize大小
// 然后对buffer分割成block
内存容器 data and coding
data和coding相当于是encoder的内存容器
- coding是每次malloc为0的
- data是直接指向jread进内存的block
在一次encode过程中,
/* Allocate data and coding */
// data是 k 个一维数组
data = (char **)malloc(sizeof(char*)*k);
coding = (char **)malloc(sizeof(char*)*m);
for (i = 0; i < m; i++) {
// coding是 初始化(malloc)出来的
// 用于存放编码之后的结果
coding[i] = (char *)malloc(sizeof(char)*blocksize);
if (coding[i] == NULL) { perror("malloc"); exit(1); }
}
...
...
if (total < size && total+buffersize <= size) {
// 一次读入一个buffersize大小到 block中
total += jfread(block, sizeof(char), buffersize, fp);
}
...
/* Set pointers to point to file data */
// data直接指向block
for (i = 0; i < k; i++) {
data[i] = block+(i*blocksize);
}
生成coding_matrix
// 生成一个matrix 对应于 tech, 这里生成的是范德蒙德矩阵
// 可以打印一下这个矩阵来看具体是什么样的
matrix = reed_sol_vandermonde_coding_matrix(k, m, w);
printf("打印matrix: \n");
for(int i = 0; i < m; i++)
{
for(int j = 0; j<k; j++)
{
printf("%d ", matrix[i]);
}
printf("\n");
}
// 这里生成的范德蒙德矩阵很奇怪
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
// 根据data中的数据块进行编码,结果放置在coding中
jerasure_matrix_encode(k, m, w, matrix, data, coding, blocksize);
写入文件
// 将data分片写入到各个文件中
// 即 k文件中的相邻两个block在源文件中的间隔为 buffersize
fwrite(data[i-1], sizeof(char), blocksize, fp2);
// coding同理
fwrite(coding[i-1], sizeof(char), blocksize, fp2);
速度计算
// totalsec = gen_matrix + encoding 算法层面的时间开销
printf("Encoding (MB/sec): %0.10f\n", (((double) size)/1024.0/1024.0)/totalsec);
// tsec 是程序全部的时间开销(系统的开销)
printf("En_Total (MB/sec): %0.10f\n", (((double) size)/1024.0/1024.0)/tsec);
对于storage而言,在算法层面的开销并不是我们努力的方向,我们应当针对系统的IO开销,并发度等角度进行优化,这样对于系统整体性能的提升更加有效
decoder
./decoder "/home/han/data/Jerasure/demo_data/pic.jpg"
读取meta文件中的参数
// encoder中写入meta文件的code
sprintf(fname, "%s/Coding/%s_meta.txt", curdir, s1);
fp2 = fopen(fname, "wb");
fprintf(fp2, "%s\n", argv[1]); // filename
fprintf(fp2, "%d\n", size); // file_size
fprintf(fp2, "%d %d %d %d %d\n", k, m, w, packetsize, buffersize); // 参数
fprintf(fp2, "%s\n", argv[4]); // tech
fprintf(fp2, "%d\n", tech); // (enum Coding_Technique)Reed_Sol_Van = 0
fprintf(fp2, "%d\n", readins); // 读入buffer的次数
fclose(fp2);
// meta.txt 中的内容
/home/han/data/Jerasure/demo_data/pic.jpg
339206
4 2 8 8 2048
reed_sol_van
0
166
decode过程
open files
在循环的逻辑中可以看到,代码中用一个fp轮询地指向4个文件
所以在一次循环中,对于4个文件的偏移是相同的,
// erased erasures 是什么????
// erased - 对k+m块的标记
// erasures - 坏块索引
sprintf(fname, "%s/Coding/%s_k%0*d%s", curdir, cs1, md, i, extension); // k块 (k从1开始)
fp = fopen(fname, "rb");
对于坏块(擦除块)
if (fp == NULL) { // 打开文件失败,意味着该块擦除
erased[i-1] = 1; // 标记
erasures[numerased] = i-1; // 坏块索引
numerased++;
//printf("%s failed\n", fname);
}
打开成功
对于没有丢失的块,如何判断将其作为decode所用的块? - 不考虑,这里是对坏块擦除之后的decode处理过程
malloc data容器 (幸存块对应的)
根据buffersize是否收缩过进行选择分支
else { // 打开文件成功
if (buffersize == origsize) { // buffersize == origsize 意味着buffersize是收缩到origsize的
stat(fname, &status);
blocksize = status.st_size;
data[i-1] = (char *)malloc(sizeof(char)*blocksize);
// 将 k_file 读入 data容器中
assert(blocksize == fread(data[i-1], sizeof(char), blocksize, fp));
}
else { // 如果buffersize 不等于 初始size
// (我这里的buffersize是调整之后的buffersize - 2048)
// 需要对 文件指针 进行 n*blocksize 的偏移
fseek(fp, blocksize*(n-1), SEEK_SET);
// 这里的blocksize和 buffersize/k不同吗?
// 如果buffersize是收缩之后的, 用buffersize/k就会出错
// 但是为什么不统一使用 blocksize呢?
//因为在这个逻辑分支中,blocksize = 0 (丑)
assert(buffersize/k == fread(data[i-1], sizeof(char), buffersize/k, fp));
}
fclose(fp);
}
data容器
malloc data容器 (擦除块对应的)
if (n == 1) {
for (i = 0; i < numerased; i++) {
if (erasures[i] < k) {
// 幸存块 对应的data容器已经在上面的open_file流程中 malloc并fread好了
// 擦除块 的data则需要malloc然后重新计算
data[erasures[i]] = (char *)malloc(sizeof(char)*blocksize);
}
else {
coding[erasures[i]-k] = (char *)malloc(sizeof(char)*blocksize);
}
}
}
Decoding
if (tech == Reed_Sol_Van || tech == Reed_Sol_R6_Op) {
// 调库接口
i = jerasure_matrix_decode(k, m, w, matrix, 1, erasures, data, coding, blocksize);
}
jerasure_matrix_decode
这里存在一个优化策略 - row_k_ones
如果matrix的第一行是全1,可以借此优化一下
因为如果是这种情况,易知 m1 = k1 + k2 + k3 + k4
如果此时的data块只擦除了一块,而且m1块没有擦除,显然是可以较容易地不需要decode_matrix就能够decode出来的
int jerasure_matrix_decode(int k, int m, int w, int *matrix, int row_k_ones, int *erasures,
char **data_ptrs, char **coding_ptrs, int size)
{
// 通过 erasures 得到 erased, 坏块索引
erased = jerasure_erasures_to_erased(k, m, erasures);
/* Find the number of data drives failed */
lastdrive = k;
edd = 0;
for (i = 0; i < k; i++) {
if (erased[i]) {
edd++; // 得到坏块数量
lastdrive = i; // 得到最后一块坏块的编号
}
}
// 如果 没有采用加速 或者是 编码块的第一块坏了, 则将last设置为k
// 换句话说, 如果可以加速, 则lastdrive不是k, 而是小于k的最后一个坏掉的数据块
if (!row_k_ones || erased[k]) lastdrive = k;
dm_ids = NULL;
decoding_matrix = NULL;
// 对于不能直接通关的情况 -
// 只有在 可以加速而且 第一个编码块没有坏掉的情况下 才可以直接通关
// 直接通关不需要建立 decoding_matrix
if (edd > 1 || (edd > 0 && (!row_k_ones || erased[k]))) {
dm_ids = talloc(int, k);
decoding_matrix = talloc(int, k*k);
jerasure_make_decoding_matrix(k, m, w, matrix, erased, decoding_matrix, dm_ids);
}
// decode - k_data
for (i = 0; edd > 0 && i < lastdrive; i++) {
if (erased[i]) {
printf(">> 111\n");
jerasure_matrix_dotprod(k, w, decoding_matrix+(i*k), dm_ids, i, data_ptrs, coding_ptrs, size);
edd--;
}
}
// decode lastdrive 只要m1没有坏掉就会调用这里?
if (edd > 0) {
tmpids = talloc(int, k);
for (i = 0; i < k; i++) {
tmpids[i] = (i < lastdrive) ? i : i+1;
}
printf(">> 222\n");
jerasure_matrix_dotprod(k, w, matrix, tmpids, lastdrive, data_ptrs, coding_ptrs, size);
}
// decode m_coding
for (i = 0; i < m; i++) {
if (erased[k+i]) {
printf(">> 333\n");
jerasure_matrix_dotprod(k, w, matrix+(i*k), NULL, i+k, data_ptrs, coding_ptrs, size);
}
}
}
decode过程
-
先对 k 个data块
-
jerasure_matrix_dotprod(k, w, decoding_matrix+(i*k), dm_ids, i, data_ptrs, coding_ptrs, size);
-
-
对 lastdrive 对应的块
-
jerasure_matrix_dotprod(k, w, matrix, tmpids, lastdrive, data_ptrs, coding_ptrs, size);
-
-
对 m 个decoding块
-
jerasure_matrix_dotprod(k, w, matrix+(i*k), NULL, i+k, data_ptrs, coding_ptrs, size);
-
-
k1 - lastdrive = 0 - 调用分支2
-
k2 - lastdrive = 1 - 2
-
k3 - lastdrive = 2 - 2
-
k4 - lastdrive = 3 - 2
-
m1 - lastdrive = 4 - 3
-
m2 - lastdrive = 4 - 3
-
k4,m1 - lastdrive = 4 - 1,3
- 因为m1坏掉,所以lastdrive = k = 4
-
k1,k2 - lastdrive = 1 - 1,2
-
k2,k4 - lastdrive = 3 - 1,2
-
m1,m2 - 3
-
k3,m2 - lastdrive = 2, - 2,3
一些优化策略:
如果只坏了 0个k块, 或者擦除1个而且 m1没有擦除,这种情况下不需要生成decodingmatrix矩阵,只需要用matrix就行
lastdrive的优化策略
在m1幸存的条件下,lastdrive是幸存块的最后一块
在m1擦除的情况下,lastdrive是m1
对于lastdrive块的decode,可以独立于k块的decode,举个例子,如果是k1,k2坏掉,那么k2是lastdrive,此时k1通过 1 来复原, 而k2通过 2 来复原
(如果lastdrive是m1的情况下,此时不会触发lastdrive逻辑分支,而是会转到 m块的decode逻辑分支)
Create decoded file
写入文件,只会还原出文件,但是不会还原 块文件
for (i = 0; i < k; i++) {
if (total+blocksize <= origsize) { // 判断是整块还是最后一块
fwrite(data[i], sizeof(char), blocksize, fp); // 将data写入到文件中
total+= blocksize;
}
else {
for (j = 0; j < blocksize; j++) {
if (total < origsize) {
fprintf(fp, "%c", data[i][j]); // 按字节将剩余的tail写入文件
total++;
}
else {
break;
}
}
}
}