Files
MultiPar/source/par2j/reedsolomon.c
2024-11-30 13:06:17 +09:00

1051 lines
37 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// reedsolomon.c
// Copyright : 2024-11-30 Yutaka Sawada
// License : GPL
#ifndef _UNICODE
#define _UNICODE
#endif
#ifndef UNICODE
#define UNICODE
#endif
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0601 // Windows 7 or later
#endif
#include <malloc.h>
#include <process.h>
#include <stdio.h>
#include <windows.h>
#include "common2.h"
#include "crc.h"
#include "gf16.h"
#include "phmd5.h"
#include "lib_opencl.h"
#include "rs_encode.h"
#include "rs_decode.h"
#include "reedsolomon.h"
#ifdef TIMER
#include <time.h>
#endif
// GPU を使う最小データサイズ (MB 単位)
// GPU の起動には時間がかかるので、データが小さすぎると逆に遅くなる
#define GPU_DATA_LIMIT 200
// GPU を使う最小ブロックサイズとブロック数
// CPU と GPU で処理を割り振る為には、ある程度のブロック数を必要とする
#define GPU_BLOCK_SIZE_LIMIT 65536
#define GPU_SOURCE_COUNT_LIMIT 192
#define GPU_PARITY_COUNT_LIMIT 8
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// chunk がキャッシュに収まるようにすれば速くなる! (Cache Blocking という最適化手法)
int try_cache_blocking(int unit_size)
{
int limit_size, chunk_count, chunk_size, cache_line_diff;
// CPUキャッシュをどのくらいまで使うか
limit_size = cpu_flag & 0x7FFF0000; // 最低でも 64KB になる
if (limit_size == 0) // キャッシュ・サイズを取得できなかった場合は最適化しない
return unit_size;
// キャッシュにうまく収まるように chunk のサイズを決める
cache_line_diff = 64 - sse_unit; // cache line size とデータ境界の差
if (cache_line_diff < 0)
cache_line_diff = 0;
chunk_count = 1;
chunk_size = unit_size; // unit_size は sse_unit の倍数になってる
while (chunk_size + cache_line_diff > limit_size){ // 制限サイズより大きいなら
// 分割数を増やして chunk のサイズを試算してみる
chunk_count++;
chunk_size = (unit_size + chunk_count - 1) / chunk_count;
chunk_size = (chunk_size + (sse_unit - 1)) & ~(sse_unit - 1); // sse_unit の倍数にする
}
return chunk_size;
}
// 空きメモリー量からファイル・アクセスのバッファー・サイズを計算する
// io_size = unit_size - HASH_SIZE になることに注意 (alloc_unit >= HASH_SIZE)
unsigned int get_io_size(
unsigned int buf_num, // 何ブロック分の領域を確保するのか
unsigned int *part_num, // 部分的なエンコード用の作業領域
size_t trial_alloc, // 確保できるか確認するのか
int alloc_unit) // メモリー単位の境界 (sse_unit か MEM_UNIT)
{
unsigned int unit_size, io_size, part_max, part_min;
size_t mem_size, io_size64;
if (part_num == NULL){ // 指定が無ければ調節しない
part_max = 0;
part_min = 0;
} else {
part_max = *part_num; // 初期値には最大値をセットする
part_min = source_num >> PART_MIN_RATE;
part_min = (part_min / cpu_num) * cpu_num; // cpu_num の倍数にする(切り下げ)
if ((int)part_min < cpu_num * 2)
part_min = cpu_num * 2; // ダブル・バッファリングするなら cpu_num の倍以上にすること
if (part_min > part_max)
part_min = part_max;
#ifdef TIMER
printf("get_io_size: part_min = %d, part_max = %d\n", part_min, part_max);
#endif
}
// alloc_unit の倍数にする
unit_size = (block_size + HASH_SIZE + (alloc_unit - 1)) & ~(alloc_unit - 1);
if (trial_alloc){
__int64 possible_size;
possible_size = (__int64)unit_size * (buf_num + part_max);
#ifndef _WIN64 // 32-bit 版なら
if (possible_size > MAX_MEM_SIZE) // 確保する最大サイズを 2GB までにする
possible_size = MAX_MEM_SIZE;
if (check_OS64() == 0){ // 32-bit OS 上なら更に制限する
if (possible_size > MAX_MEM_SIZE32)
possible_size = MAX_MEM_SIZE32;
}
#endif
trial_alloc = (size_t)possible_size;
trial_alloc = (trial_alloc + 0xFFFF) & ~0xFFFF; // 64KB の倍数にしておく
}
mem_size = get_mem_size(trial_alloc);
io_size64 = mem_size / (buf_num + part_max) - HASH_SIZE; // 何個分必要か
// ブロック・サイズより大きい、またはブロック・サイズ自体が小さい場合は
if ((io_size64 >= (size_t)block_size) || (block_size <= 1024)){
io_size = unit_size - HASH_SIZE; // ブロック・サイズ - HASH_SIZE
} else { // ブロック・サイズを等分割する
unsigned int num, num2;
io_size = (unsigned int)io_size64;
num = (block_size + io_size - 1) / io_size; // ブロックを何分割するか
if (part_min < part_max){ // 保持する量に幅があるなら
io_size64 = mem_size / (buf_num + part_min) - HASH_SIZE; // 確保するサイズを最低限にした場合
if (io_size64 >= (size_t)block_size){
num2 = 1;
} else {
io_size = (unsigned int)io_size64;
num2 = (block_size + io_size - 1) / io_size;
}
} else {
num2 = num;
}
if (num > num2){ // 確保量を減らしたほうがブロックの分割数が減るなら
io_size = (block_size + num2 - 1) / num2;
if (io_size < 1024)
io_size = 1024;
num = (unsigned int)(mem_size / (io_size + HASH_SIZE)) - buf_num;
if (num < part_max){ // 分割して計算するなら
num2 = (parity_num + num - 1) / num; // 分割回数
num = (parity_num + num2 - 1) / num2;
num = ((num + cpu_num - 1) / cpu_num) * cpu_num; // cpu_num の倍数にする(切り上げ)
if (num < part_min)
num = part_min;
}
if (num > part_max)
num = part_max;
*part_num = num;
} else {
io_size = (block_size + num - 1) / num;
if (io_size < 1024)
io_size = 1024; // 断片化する場合でもブロック数が多いと 32768 KB は使う
}
io_size = ((io_size + HASH_SIZE + (alloc_unit - 1)) & ~(alloc_unit - 1)) - HASH_SIZE; // alloc_unit の倍数 - HASH_SIZE
}
return io_size;
}
// 何ブロックまとめてファイルから読み込むかを空きメモリー量から計算する
int read_block_num(
int keep_num, // 保持するパリティ・ブロック数
size_t trial_alloc, // 確保できるか確認するのか
int alloc_unit) // メモリー単位の境界 (sse_unit か MEM_UNIT)
{
int buf_num, read_min;
unsigned int unit_size;
size_t mem_size;
read_min = keep_num >> READ_MIN_RATE;
if (read_min < READ_MIN_NUM)
read_min = READ_MIN_NUM;
if (read_min > source_num)
read_min = source_num;
unit_size = (block_size + HASH_SIZE + (alloc_unit - 1)) & ~(alloc_unit - 1);
if (trial_alloc){
__int64 possible_size;
possible_size = (__int64)unit_size * (source_num + keep_num);
#ifndef _WIN64 // 32-bit 版なら
if (possible_size > MAX_MEM_SIZE) // 確保する最大サイズを 2GB までにする
possible_size = MAX_MEM_SIZE;
if (check_OS64() == 0){ // 32-bit OS 上なら更に制限する
if (possible_size > MAX_MEM_SIZE32)
possible_size = MAX_MEM_SIZE32;
}
#endif
trial_alloc = (size_t)possible_size;
trial_alloc = (trial_alloc + 0xFFFF) & ~0xFFFF; // 64KB の倍数にしておく
}
mem_size = get_mem_size(trial_alloc) / unit_size; // 何個分確保できるか
if (mem_size >= (size_t)(source_num + keep_num)){ // 最大個数より多い
buf_num = source_num;
} else if ((int)mem_size < read_min + keep_num){ // 少なすぎる
buf_num = 0; // メモリー不足の印
} else { // ソース・ブロック個数を等分割する
int split_num;
buf_num = (int)mem_size - keep_num;
split_num = (source_num + buf_num - 1) / buf_num; // 何回に別けて読み込むか
buf_num = (source_num + split_num - 1) / split_num;
}
return buf_num;
}
// 1st encode, decode を何スレッドで実行するか決める
int calc_thread_num1(int max_num)
{
int i, num;
// 読み込み中はスレッド数を減らす(シングル・スレッドの時は 0にする
num = 0;
i = 1;
while (i * 2 <= cpu_num){ // 1=0, 2~3=1, 4~7=2, 8~15=3, 16~31=4, 32=5
num++;
i *= 2;
}
if (num > max_num)
num = max_num;
return num;
}
// 1st & 2nd encode, decode を何スレッドで実行するか決める
int calc_thread_num2(int max_num, int *cpu_num2)
{
int i, num1, num2;
// 読み込み中はスレッド数を減らす(シングル・スレッドの時は 0にする
num1 = 0;
i = 2;
while (i <= cpu_num){ // 1=0, 2~3=1, 4~7=2, 8~15=3, 16~31=4, 32=5
num1++;
i *= 2;
}
if (num1 > max_num)
num1 = max_num;
// CPU と GPU で必ず2スレッド使う
num2 = cpu_num;
if (num2 < 2)
num2 = 2;
*cpu_num2 = num2;
return num1;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// 戸川 隼人 の「演習と応用FORTRAN77」の逆行列の計算方法を参考にして
// Gaussian Elimination を少し修正して行列の数を一つにしてみた
// 半分のメモリーで逆行列を計算する (利用するパリティ・ブロックの所だけ)
static int invert_matrix_st(unsigned short *mat,
int rows, // 横行の数、行列の縦サイズ、失われたソース・ブロックの数 = 利用するパリティ・ブロック数
int cols, // 縦列の数、行列の横サイズ、本来のソース・ブロック数
source_ctx_r *s_blk) // 各ソース・ブロックの情報
{
int i, j, row_start, row_start2, pivot, factor;
unsigned int time_last = GetTickCount();
// Gaussian Elimination with 1 matrix
pivot = 0;
row_start = 0; // その行の開始位置
for (i = 0; i < rows; i++){
// 経過表示
if (GetTickCount() - time_last >= UPDATE_TIME){
if (print_progress((i * 1000) / rows))
return 2;
time_last = GetTickCount();
}
// その行 (パリティ・ブロック) がどのソース・ブロックの代用か
while ((pivot < cols) && (s_blk[pivot].exist != 0))
pivot++;
// Divide the row by element i,pivot
factor = mat[row_start + pivot]; // mat(j, pivot) は 0以外のはず
//printf("\nparity[ %u ] -> source[ %u ], factor = %u\n", id[col_find], col_find, factor);
if (factor > 1){ // factor が 1より大きいなら、1にする為に factor で割る
mat[row_start + pivot] = 1; // これが行列を一個で済ます手
galois_region_divide(mat + row_start, cols, factor);
} else if (factor == 0){ // factor = 0 だと、その行列の逆行列を計算できない
return (0x00010000 | pivot); // どのソース・ブロックで問題が発生したのかを返す
}
// 別の行の同じ pivot 列が 0以外なら、その値を 0にするために、
// i 行を何倍かしたものを XOR する
for (j = rows - 1; j >= 0; j--){
if (j == i)
continue; // 同じ行はとばす
row_start2 = cols * j; // その行の開始位置
factor = mat[row_start2 + pivot]; // j 行の pivot 列の値
mat[row_start2 + pivot] = 0; // これが行列を一個で済ます手
// 先の計算により、i 行の pivot 列の値は必ず 1なので、この factor が倍率になる
galois_region_multiply(mat + row_start, mat + row_start2, cols, factor);
}
row_start += cols; // 次の行にずらす
pivot++;
}
return 0;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// マルチ・プロセッサー対応
/*
typedef struct { // RS threading control struct
unsigned short *mat; // 行列
int cols; // 横行の長さ
volatile int start; // 掛ける行の先頭位置
volatile int pivot; // 倍率となる値の位置
volatile int skip; // とばす行
volatile int now; // 消去する行
HANDLE h;
HANDLE run;
HANDLE end;
} INV_TH;
// サブ・スレッド
static DWORD WINAPI thread_func(LPVOID lpParameter)
{
unsigned short *mat;
int j, cols, row_start2, factor;
INV_TH *th;
th = (INV_TH *)lpParameter;
mat = th->mat;
cols = th->cols;
WaitForSingleObject(th->run, INFINITE); // 計算開始の合図を待つ
while (th->skip >= 0){
while ((j = InterlockedDecrement(&(th->now))) >= 0){ // j = --th_now
if (j == th->skip)
continue;
row_start2 = cols * j; // その行の開始位置
factor = mat[row_start2 + th->pivot]; // j 行の pivot 列の値
mat[row_start2 + th->pivot] = 0; // これが行列を一個で済ます手
// 先の計算により、i 行の pivot 列の値は必ず 1なので、この factor が倍率になる
galois_region_multiply(mat + th->start, mat + row_start2, cols, factor);
}
//_mm_sfence(); // メモリーへの書き込みを完了する
SetEvent(th->end); // 計算終了を通知する
WaitForSingleObject(th->run, INFINITE); // 計算開始の合図を待つ
}
// 終了処理
CloseHandle(th->run);
CloseHandle(th->end);
return 0;
}
*/
typedef struct { // Maxtrix Inversion threading control struct
unsigned short *mat; // 行列
int cols; // 横行の長さ
volatile int start; // 掛ける行の先頭位置
volatile int pivot; // 倍率となる値の位置
volatile int skip; // とばす行
volatile int now; // 消去する行
HANDLE run;
HANDLE end;
} INV_TH;
// サブ・スレッド
static DWORD WINAPI thread_func(LPVOID lpParameter)
{
unsigned short *mat;
int j, cols, row_start2, factor;
HANDLE hRun, hEnd;
INV_TH *th;
th = (INV_TH *)lpParameter;
mat = th->mat;
cols = th->cols;
hRun = th->run;
hEnd = th->end;
SetEvent(hEnd); // 設定完了を通知する
WaitForSingleObject(hRun, INFINITE); // 計算開始の合図を待つ
while (th->skip >= 0){
while ((j = InterlockedDecrement(&(th->now))) >= 0){ // j = --th_now
if (j == th->skip)
continue;
row_start2 = cols * j; // その行の開始位置
factor = mat[row_start2 + th->pivot]; // j 行の pivot 列の値
mat[row_start2 + th->pivot] = 0; // これが行列を一個で済ます手
// 先の計算により、i 行の pivot 列の値は必ず 1なので、この factor が倍率になる
galois_region_multiply(mat + th->start, mat + row_start2, cols, factor);
}
//_mm_sfence(); // メモリーへの書き込みを完了する
SetEvent(hEnd); // 計算終了を通知する
WaitForSingleObject(hRun, INFINITE); // 計算開始の合図を待つ
}
// 終了処理
CloseHandle(hRun);
CloseHandle(hEnd);
return 0;
}
// マルチ・スレッドで逆行列を計算する (利用するパリティ・ブロックの所だけ)
/*
static int invert_matrix_mt(unsigned short *mat,
int rows, // 横行の数、行列の縦サイズ、失われたソース・ブロックの数 = 利用するパリティ・ブロック数
int cols, // 縦列の数、行列の横サイズ、本来のソース・ブロック数
source_ctx_r *s_blk) // 各ソース・ブロックの情報
{
int j, row_start2, factor;
unsigned int time_last = GetTickCount();
INV_TH th[1];
memset(th, 0, sizeof(INV_TH));
// イベントを作成する
th->run = CreateEvent(NULL, FALSE, FALSE, NULL); // 両方とも Auto Reset にする
if (th->run == NULL){
print_win32_err();
printf("error, inv-thread\n");
return 1;
}
th->end = CreateEvent(NULL, FALSE, FALSE, NULL);
if (th->end == NULL){
print_win32_err();
CloseHandle(th->run);
printf("error, inv-thread\n");
return 1;
}
// サブ・スレッドを起動する
th->mat = mat;
th->cols = cols;
//_mm_sfence(); // メモリーへの書き込みを完了してからスレッドを起動する
th->h = (HANDLE)_beginthreadex(NULL, STACK_SIZE, thread_func, (LPVOID)th, 0, NULL);
if (th->h == NULL){
print_win32_err();
CloseHandle(th->run);
CloseHandle(th->end);
printf("error, inv-thread\n");
return 1;
}
// Gaussian Elimination with 1 matrix
th->pivot = 0;
th->start = 0; // その行の開始位置
for (th->skip = 0; th->skip < rows; th->skip++){
// 経過表示
if (GetTickCount() - time_last >= UPDATE_TIME){
if (print_progress((th->skip * 1000) / rows)){
th->skip = -1; // 終了指示
//_mm_sfence();
SetEvent(th->run);
WaitForSingleObject(th->h, INFINITE);
CloseHandle(th->h);
return 2;
}
time_last = GetTickCount();
}
// その行 (パリティ・ブロック) がどのソース・ブロックの代用か
while ((th->pivot < cols) && (s_blk[th->pivot].exist != 0))
th->pivot++;
// Divide the row by element i,pivot
factor = mat[th->start + th->pivot];
if (factor > 1){
mat[th->start + th->pivot] = 1; // これが行列を一個で済ます手
galois_region_divide(mat + th->start, cols, factor);
} else if (factor == 0){ // factor = 0 だと、その行列の逆行列を計算できない
th->skip = -1; // 終了指示
//_mm_sfence();
SetEvent(th->run);
WaitForSingleObject(th->h, INFINITE);
CloseHandle(th->h);
return (0x00010000 | th->pivot); // どのソース・ブロックで問題が発生したのかを返す
}
// 別の行の同じ pivot 列が 0以外なら、その値を 0にするために、
// i 行を何倍かしたものを XOR する
th->now = rows; // 初期値 + 1
//_mm_sfence(); // メモリーへの書き込みを完了してからスレッドを再開する
SetEvent(th->run); // サブ・スレッドに計算を開始させる
while ((j = InterlockedDecrement(&(th->now))) >= 0){ // j = --th_now
if (j == th->skip) // 同じ行はとばす
continue;
row_start2 = cols * j; // その行の開始位置
factor = mat[row_start2 + th->pivot]; // j 行の pivot 列の値
mat[row_start2 + th->pivot] = 0; // これが行列を一個で済ます手
// 先の計算により、i 行の pivot 列の値は必ず 1なので、この factor が倍率になる
galois_region_multiply(mat + th->start, mat + row_start2, cols, factor);
}
WaitForSingleObject(th->end, INFINITE); // サブ・スレッドの計算終了の合図を待つ
th->start += cols;
th->pivot++;
}
// サブ・スレッドを終了させる
th->skip = -1; // 終了指示
//_mm_sfence();
SetEvent(th->run);
WaitForSingleObject(th->h, INFINITE);
CloseHandle(th->h);
return 0;
}
*/
static int invert_matrix_mt(unsigned short *mat,
int rows, // 横行の数、行列の縦サイズ、失われたソース・ブロックの数 = 利用するパリティ・ブロック数
int cols, // 縦列の数、行列の横サイズ、本来のソース・ブロック数
source_ctx_r *s_blk) // 各ソース・ブロックの情報
{
int err = 0, j, row_start2, factor, sub_num;
unsigned int time_last = GetTickCount();
HANDLE hSub[MAX_CPU / 2], hRun[MAX_CPU / 2], hEnd[MAX_CPU / 2];
INV_TH th[1];
memset(hSub, 0, sizeof(HANDLE) * (MAX_CPU / 2));
memset(th, 0, sizeof(INV_TH));
// サブ・スレッドの数は平方根(切り上げ)にする
sub_num = 1;
j = 2;
while (j < cpu_num){ // 1~2=1, 3~4=2, 5~8=3, 9~16=4, 17~32=5
sub_num++;
j *= 2;
}
if (sub_num > rows - 2)
sub_num = rows - 2; // 多過ぎても意味ないので制限する
#ifdef TIMER
// 使うスレッド数は、メイン・スレッドの分も含めるので 1個増える
printf("\nMaxtrix Inversion with %d threads\n", sub_num + 1);
#endif
// サブ・スレッドを起動する
th->mat = mat;
th->cols = cols;
for (j = 0; j < sub_num; j++){ // サブ・スレッドごとに
// イベントを作成する
hRun[j] = CreateEvent(NULL, FALSE, FALSE, NULL); // 両方とも Auto Reset にする
if (hRun[j] == NULL){
print_win32_err();
printf("error, inv-thread\n");
err = 1;
goto error_end;
}
hEnd[j] = CreateEvent(NULL, FALSE, FALSE, NULL);
if (hEnd[j] == NULL){
print_win32_err();
CloseHandle(hRun[j]);
printf("error, inv-thread\n");
err = 1;
goto error_end;
}
// サブ・スレッドを起動する
th->run = hRun[j];
th->end = hEnd[j];
//_mm_sfence(); // メモリーへの書き込みを完了してからスレッドを起動する
hSub[j] = (HANDLE)_beginthreadex(NULL, STACK_SIZE, thread_func, (LPVOID)th, 0, NULL);
if (hSub[j] == NULL){
print_win32_err();
CloseHandle(hRun[j]);
CloseHandle(hEnd[j]);
printf("error, inv-thread\n");
err = 1;
goto error_end;
}
WaitForSingleObject(hEnd[j], INFINITE); // 設定終了の合図を待つ (リセットする)
}
// Gaussian Elimination with 1 matrix
th->pivot = 0;
th->start = 0; // その行の開始位置
for (th->skip = 0; th->skip < rows; th->skip++){
// 経過表示
if (GetTickCount() - time_last >= UPDATE_TIME){
if (print_progress((th->skip * 1000) / rows)){
err = 2;
goto error_end;
}
time_last = GetTickCount();
}
// その行 (パリティ・ブロック) がどのソース・ブロックの代用か
while ((th->pivot < cols) && (s_blk[th->pivot].exist != 0))
th->pivot++;
// Divide the row by element i,pivot
factor = mat[th->start + th->pivot];
if (factor > 1){
mat[th->start + th->pivot] = 1; // これが行列を一個で済ます手
galois_region_divide(mat + th->start, cols, factor);
} else if (factor == 0){ // factor = 0 だと、その行列の逆行列を計算できない
err = (0x00010000 | th->pivot); // どのソース・ブロックで問題が発生したのかを返す
goto error_end;
}
// 別の行の同じ pivot 列が 0以外なら、その値を 0にするために、
// i 行を何倍かしたものを XOR する
th->now = rows; // 初期値 + 1
//_mm_sfence(); // メモリーへの書き込みを完了してからスレッドを再開する
for (j = 0; j < sub_num; j++)
SetEvent(hRun[j]); // サブ・スレッドに計算を開始させる
while ((j = InterlockedDecrement(&(th->now))) >= 0){ // j = --th_now
if (j == th->skip) // 同じ行はとばす
continue;
row_start2 = cols * j; // その行の開始位置
factor = mat[row_start2 + th->pivot]; // j 行の pivot 列の値
mat[row_start2 + th->pivot] = 0; // これが行列を一個で済ます手
// 先の計算により、i 行の pivot 列の値は必ず 1なので、この factor が倍率になる
galois_region_multiply(mat + th->start, mat + row_start2, cols, factor);
}
WaitForMultipleObjects(sub_num, hEnd, TRUE, INFINITE); // サブ・スレッドの計算終了の合図を待つ
th->start += cols;
th->pivot++;
}
error_end:
InterlockedExchange(&(th->skip), -1); // 終了指示
for (j = 0; j < sub_num; j++){
if (hSub[j]){ // サブ・スレッドを終了させる
SetEvent(hRun[j]);
WaitForSingleObject(hSub[j], INFINITE);
CloseHandle(hSub[j]);
}
}
return err;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
gflib の行列作成用関数や行列の逆変換用の関数を元にして、
計算のやり方を PAR 2.0 用に修正する。
par-v1.1.tar.gz に含まれる rs.doc
Dummies guide to Reed-Solomon coding. を参考にする
*/
/*
5 * 5 なら
1 1 1 1 1 constant の 0乗
2 4 16 128 256 <- この行の値を constant とする
4 16 256 16384 4107 constant の 2乗
8 64 4096 8566 7099 constant の 3乗
16 256 4107 43963 7166 constant の 4乗
par2-specifications.pdf によると、constant は 2の乗数で、
その指数は (n%3 != 0 && n%5 != 0 && n%17 != 0 && n%257 != 0) になる。
*/
// PAR 2.0 のパリティ検査行列はエンコード中にその場で生成する
// constant と facter の 2個のベクトルで表現する
// パリティ・ブロックごとに facter *= constant で更新していく
static void make_encode_constant(
unsigned short *constant) // constant を収めた配列
{
unsigned short temp;
int n, i;
// constant は 2の乗数で、係数が3,5,17,257の倍数になるものは除く
// 定数 2, 4, 16, 128, 256, 2048, 8192, ...
n = 0;
temp = 1;
for (i = 0; i < source_num; i++){
while (n <= 65535){
temp = galois_multiply_fix(temp, 1); // galois_multiply(temp, 2);
n++;
if ((n % 3 != 0) && (n % 5 != 0) && (n % 17 != 0) && (n % 257 != 0))
break;
}
constant[i] = temp;
}
}
// 復元用の行列を作る、十分な数のパリティ・ブロックが必要
static int make_decode_matrix(
unsigned short *mat, // 復元用の行列
int block_lost, // 横行、行列の縦サイズ、失われたソース・ブロックの数 = 必要なパリティ・ブロック数
source_ctx_r *s_blk, // 各ソース・ブロックの情報
parity_ctx_r *p_blk) // 各パリティ・ブロックの情報
{
unsigned short *id; // 失われたソース・ブロックをどのパリティ・ブロックで代用したか
unsigned short constant;
int i, j, k, n;
// printf("\n parity_num = %d, rows = %d, cols = %d \n", parity_num, block_lost, source_num);
// 失われたソース・ブロックをどのパリティ・ブロックで代用するか
id = mat + (block_lost * source_num);
j = 0;
for (i = 0; (i < parity_num) && (j < block_lost); i++){
if (p_blk[i].exist == 1) // 利用不可の印が付いてるブロックは無視する
id[j++] = (unsigned short)i;
}
if (j < block_lost){ // パリティ・ブロックの数が足りなければ
printf("need more recovery slice\n");
return 1;
}
// 存在して利用するパリティ・ブロックだけの行列を作る
n = 0;
constant = 1;
for (i = 0; i < source_num; i++){ // 一列ずつ縦に値をセットしていく
while (n <= 65535){
constant = galois_multiply_fix(constant, 1); // galois_multiply(constant, 2);
n++;
if ((n % 3 != 0) && (n % 5 != 0) && (n % 17 != 0) && (n % 257 != 0))
break;
}
// printf("\n[%5d], 2 pow %5d = %5d", i, n, constant);
k = 0;
for (j = 0; j < source_num; j++){ // j 行の i 列
if (s_blk[j].exist == 0){ // 該当部分はパリティ・ブロックで補うのなら
mat[source_num * k + i] = galois_power(constant, id[k]);
k++;
}
}
}
if ((cpu_num == 1) || (source_num < 10) || (block_lost < 4)){ // 小さすぎる行列はマルチ・スレッドにしない
k = invert_matrix_st(mat, block_lost, source_num, s_blk);
} else {
k = invert_matrix_mt(mat, block_lost, source_num, s_blk);
}
return k;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// リード・ソロモン符号を使ってエンコードする
int rs_encode(
wchar_t *file_path,
unsigned char *header_buf, // Recovery Slice packet のパケット・ヘッダー
HANDLE *rcv_hFile, // リカバリ・ファイルのハンドル
file_ctx_c *files, // ソース・ファイルの情報
source_ctx_c *s_blk, // ソース・ブロックの情報
parity_ctx_c *p_blk) // パリティ・ブロックの情報
{
unsigned short *constant = NULL;
int err = 0;
unsigned int len;
#ifdef TIMER
clock_t time_total = clock();
#endif
if (galois_create_table()){
printf("galois_create_table\n");
return 1;
}
if (source_num == 1){ // ソース・ブロックが一個だけなら
err = encode_method1(file_path, header_buf, rcv_hFile, files, s_blk, p_blk);
goto error_end;
}
// パリティ計算用の行列演算の準備をする
len = sizeof(unsigned short) * source_num;
if (OpenCL_method != 0)
len *= 3; // GPU の作業領域も確保しておく
constant = malloc(len);
if (constant == NULL){
printf("malloc, %d\n", len);
err = 1;
goto error_end;
}
#ifdef TIMER
if (len & 0xFFFFF000){
printf("\nmatrix size = %u KB\n", len >> 10);
} else {
printf("\nmatrix size = %u Bytes\n", len);
}
#endif
// パリティ検査行列の基になる定数
make_encode_constant(constant);
// for (len = 0; (int)len < source_num; len++)
// printf("constant[%5d] = %5d\n", len, constant[len]);
#ifdef TIMER
err = 0; // IO method : 0=Auto, -2=Read all, -4=GPU read all
if (err == 0){
#endif
// HDD なら 1-pass & Read some 方式を使う
// メモリー不足や SSD なら、Read all 方式でブロックを断片化させる
if ((OpenCL_method != 0) && (block_size >= GPU_BLOCK_SIZE_LIMIT) &&
(source_num >= GPU_SOURCE_COUNT_LIMIT) && (parity_num >= GPU_PARITY_COUNT_LIMIT) &&
((source_num + parity_num) * (__int64)block_size > 1048576 * GPU_DATA_LIMIT)){
// ブロック数が多いなら、ブロックごとにスレッドを割り当てる (GPU を使う)
err = -4; // 2-pass & GPU read all
} else {
err = -2; // 2-pass & Read all
}
#ifdef TIMER
}
#endif
// 最初は GPUを使い、無理なら次に移る
if (err == -4)
err = encode_method4(file_path, header_buf, rcv_hFile, files, s_blk, p_blk, constant);
if (err == -2) // ソース・データを全て読み込む場合
err = encode_method2(file_path, header_buf, rcv_hFile, files, s_blk, p_blk, constant);
#ifdef TIMER
if (err != 1){
time_total = clock() - time_total;
printf("total %.3f sec\n", (double)time_total / CLOCKS_PER_SEC);
}
#endif
error_end:
if (constant)
free(constant);
galois_free_table(); // Galois Field のテーブルを解放する
return err;
}
// パリティ・ブロックをメモリー上に保持して、一度に読み書きする
int rs_encode_1pass(
wchar_t *file_path,
wchar_t *recovery_path, // 作業用
int packet_limit, // リカバリ・ファイルのパケット繰り返しの制限
int block_distri, // (3-bit目は番号の付け方)
int packet_num, // 共通パケットの数
unsigned char *common_buf, // 共通パケットのバッファー
int common_size, // 共通パケットのバッファー・サイズ
unsigned char *footer_buf, // 末尾パケットのバッファー
int footer_size, // 末尾パケットのバッファー・サイズ
HANDLE *rcv_hFile, // リカバリ・ファイルのハンドル
file_ctx_c *files, // ソース・ファイルの情報
source_ctx_c *s_blk) // ソース・ブロックの情報
{
unsigned short *constant = NULL;
int err = 0;
unsigned int len;
#ifdef TIMER
clock_t time_total = clock();
#endif
if (galois_create_table()){
printf("galois_create_table\n");
return 1;
}
// パリティ計算用の行列演算の準備をする
len = sizeof(unsigned short) * source_num;
if (OpenCL_method != 0)
len *= 3; // GPU の作業領域も確保しておく
constant = malloc(len);
if (constant == NULL){
printf("malloc, %d\n", len);
err = 1;
goto error_end;
}
#ifdef TIMER
if (len & 0xFFFFF000){
printf("\nmatrix size = %u KB\n", len >> 10);
} else {
printf("\nmatrix size = %u Bytes\n", len);
}
#endif
// パリティ検査行列の基になる定数
make_encode_constant(constant);
// for (len = 0; (int)len < source_num; len++)
// printf("constant[%5d] = %5d\n", len, constant[len]);
#ifdef TIMER
err = 0; // IO method : 0=Auto, -3=Read some, -5=GPU read some, -? = Goto 2pass
if (err == 0){
#endif
// メモリーが足りてる場合だけ 1-pass方式を使う
if ((OpenCL_method != 0) && (block_size >= GPU_BLOCK_SIZE_LIMIT) &&
(source_num >= GPU_SOURCE_COUNT_LIMIT) && (parity_num >= GPU_PARITY_COUNT_LIMIT) &&
((source_num + parity_num) * (__int64)block_size > 1048576 * GPU_DATA_LIMIT)){
err = -5; // 1-pass & GPU read some
} else {
err = -3; // 1-pass & Read some
}
#ifdef TIMER
}
#endif
// 最初は GPUを使い、無理なら次に移る
if (err == -5)
err = encode_method5(file_path, recovery_path, packet_limit, block_distri, packet_num,
common_buf, common_size, footer_buf, footer_size, rcv_hFile, files, s_blk, constant);
if (err == -3) // ソース・データをいくつか読み込む場合
err = encode_method3(file_path, recovery_path, packet_limit, block_distri, packet_num,
common_buf, common_size, footer_buf, footer_size, rcv_hFile, files, s_blk, constant);
#ifdef TIMER
if (err < 0){
printf("switching to 2-pass processing, %d\n", err);
} else if (err != 1){
time_total = clock() - time_total;
printf("total %.3f sec\n", (double)time_total / CLOCKS_PER_SEC);
}
#endif
error_end:
if (constant)
free(constant);
galois_free_table(); // Galois Field のテーブルを解放する
return err;
}
// リード・ソロモン符号を使ってデコードする
int rs_decode(
wchar_t *file_path,
int block_lost, // 失われたソース・ブロックの数
HANDLE *rcv_hFile, // リカバリ・ファイルのハンドル
file_ctx_r *files, // ソース・ファイルの情報
source_ctx_r *s_blk, // ソース・ブロックの情報
parity_ctx_r *p_blk) // パリティ・ブロックの情報
{
unsigned short *mat = NULL, *id;
int err = 0, i, j, k;
unsigned int len;
#ifdef TIMER
clock_t time_matrix = 0, time_total = clock();
#endif
if (galois_create_table()){
printf("galois_create_table\n");
return 1;
}
if (source_num == 1){ // ソース・ブロックが一個だけなら
err = decode_method1(file_path, rcv_hFile, files, s_blk, p_blk);
goto error_end;
}
// 復元用の行列演算の準備をする
len = sizeof(unsigned short) * block_lost * (source_num + 1);
mat = malloc(len);
if (mat == NULL){
printf("malloc, %d\n", len);
printf("matrix for recovery is too large\n");
err = 1;
goto error_end;
}
#ifdef TIMER
if (len & 0xFFF00000){
printf("\nmatrix size = %u MB\n", len >> 20);
} else if (len & 0x000FF000){
printf("\nmatrix size = %u KB\n", len >> 10);
} else {
printf("\nmatrix size = %u Bytes\n", len);
}
#endif
// 何番目の消失ソース・ブロックがどのパリティで代替されるか
id = mat + (block_lost * source_num);
#ifdef TIMER
time_matrix = clock();
#endif
// 復元用の行列を計算する
print_progress_text(0, "Computing matrix");
err = make_decode_matrix(mat, block_lost, s_blk, p_blk);
while (err >= 0x00010000){ // 逆行列を計算できなかった場合 ( Petr Matas の修正案を参考に実装)
printf("\n");
err ^= 0x00010000; // エラーが起きた行 (ソース・ブロックの番号)
printf("fail at input slice %d\n", err);
k = 0;
for (i = 0; i < err; i++){
if (s_blk[i].exist == 0)
k++;
}
// id[k] エラーが起きた行に対応するパリティ・ブロックの番号
p_blk[id[k]].exist = 0x100; // そのパリティ・ブロックを使わないようにする
printf("disable recovery slice %d\n", id[k]);
j = 0;
for (i = 0; i < parity_num; i++){
if (p_blk[i].exist == 1)
j++; // 利用可能なパリティ・ブロックの数
}
if (j >= block_lost){ // 使えるパリティ・ブロックの数が破損ブロックの数以上なら
print_progress_text(0, "Computing matrix");
err = make_decode_matrix(mat, block_lost, s_blk, p_blk);
} else { // 代替するパリティ・ブロックの数が足りなければ
printf("fail at recovery slice");
for (i = 0; i < parity_num; i++){
if (p_blk[i].exist == 0x100)
printf(" %d", i);
}
printf("\n");
err = 1;
}
}
if (err) // それ以外のエラーなら
goto error_end;
print_progress_done(); // 改行して行の先頭に戻しておく
//for (i = 0; i < block_lost; i++)
// printf("id[%d] = %d\n", i, id[i]);
#ifdef TIMER
time_matrix = clock() - time_matrix;
#endif
#ifdef TIMER
err = 0; // IO method : 0=Auto, -2=Read all, -3=Read some, -4=GPU all, -5=GPU some
if (err == 0){
#endif
if ((OpenCL_method != 0) && (block_size >= GPU_BLOCK_SIZE_LIMIT) &&
(source_num >= GPU_SOURCE_COUNT_LIMIT) && (block_lost >= GPU_PARITY_COUNT_LIMIT) &&
((source_num + block_lost) * (__int64)block_size > 1048576 * GPU_DATA_LIMIT)){
// ブロック数が多いなら、ブロックごとにスレッドを割り当てる (GPU を使う)
if (memory_use & 16){
err = -4; // SSD なら Read all 方式でブロックが断片化しても速い
} else if (read_block_num(block_lost * 2, 0, MEM_UNIT) != 0){
err = -5; // HDD でメモリーが足りてるなら Read some 方式を使う
} else {
err = -4; // メモリー不足なら Read all 方式でブロックを断片化させる
}
} else {
// ソース・ブロックを全て断片的に読み込むか、いくつかを丸ごと読み込むかを決める
if (memory_use & 16){
err = -2; // SSD なら Read all 方式でブロックが断片化しても速い
} else if (read_block_num(block_lost, 0, sse_unit) != 0){
err = -3; // HDD でメモリーが足りてるなら Read some 方式を使う
} else {
err = -2; // メモリー不足なら Read all 方式でブロックを断片化させる
}
}
#ifdef TIMER
}
#endif
// ファイル・アクセスの方式によって分岐する
if (err == -5)
err = decode_method5(file_path, block_lost, rcv_hFile, files, s_blk, p_blk, mat);
if (err == -4)
err = decode_method4(file_path, block_lost, rcv_hFile, files, s_blk, p_blk, mat);
if (err == -3) // ソース・データをいくつか読み込む場合
err = decode_method3(file_path, block_lost, rcv_hFile, files, s_blk, p_blk, mat);
if (err == -2) // ソース・データを全て読み込む場合
err = decode_method2(file_path, block_lost, rcv_hFile, files, s_blk, p_blk, mat);
#ifdef TIMER
if (err != 1){
time_total = clock() - time_total;
printf("total %.3f sec\n", (double)time_total / CLOCKS_PER_SEC);
printf("matrix %.3f sec\n", (double)time_matrix / CLOCKS_PER_SEC);
}
#endif
error_end:
if (mat)
free(mat);
galois_free_table(); // Galois Field のテーブルを解放する
return err;
}