Compare commits
	
		
			4 commits
		
	
	
		
			master
			...
			text-plain
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 15f33edd3a | |||
| d4996bf416 | |||
| 6abf49fc23 | |||
| 0a8359cfe7 | 
					 7 changed files with 289 additions and 791 deletions
				
			
		
							
								
								
									
										158
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										158
									
								
								README.md
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,133 +1,45 @@
 | 
			
		|||
# Implementing Change Data Capture
 | 
			
		||||
 | 
			
		||||
## Объяснение алгоритма FastCDC
 | 
			
		||||
## Объяснение изменений и особенностей
 | 
			
		||||
 | 
			
		||||
FastCDC представляет собой алгоритм для разбиения данных на "куски" (чанки) на основе их содержания, что позволяет экономить пространство в хранилищах. Этот метод быстрее и эффективнее традиционных подходов, таких как Rabin или Gear.
 | 
			
		||||
1. **Статическая таблица Gear**:
 | 
			
		||||
   - Таблица `gear` определена как `immutable ulong[256]` с 256 случайными 64-битными числами, сгенерированными заранее (вместо динамической генерации с `Random`). Значения выбраны произвольно, но в стиле реализаций FastCDC, где таблица фиксирована для консистентности.
 | 
			
		||||
   - Использование `immutable` обеспечивает неизменяемость и оптимизацию компилятором D.
 | 
			
		||||
   - В реальных системах таблица может быть взята из оригинальной статьи FastCDC или сгенерирована с криптографически безопасным ГСЧ, но здесь она статична для простоты и воспроизводимости.
 | 
			
		||||
 | 
			
		||||
### Шаг 1: Что Такое Content-Defined Chunking (CDC) и Зачем Это Нужно?
 | 
			
		||||
Рассмотрим большой файл, например, видео или архив. При изменении нескольких байт весь файл может восприниматься как новый, что приводит к записи дубликатов и потере места. CDC решает эту проблему: данные разбиваются на переменные куски (чанки) не по фиксированной длине (например, 8 КБ), а по содержанию. При небольших изменениях большинство чанков остаются неизменными и не требуют перезаписи.
 | 
			
		||||
2. **Текст**:
 | 
			
		||||
   - Переносы строк (`\n`) заменены на пробелы, как указано в запросе. Длина текста в UTF-8 составляет 7471 байт (кириллица — 2 байта на символ, пробелы и знаки — 1 байт).
 | 
			
		||||
   - Текст конвертируется в `ubyte[]` для обработки FastCDC, который работает на уровне байтов.
 | 
			
		||||
 | 
			
		||||
- **Проблема с фиксированным разбиением (Fixed-Size Chunking)**: Вставка байта в начало сдвигает все чанки, и система считает весь файл новым.
 | 
			
		||||
- **Как работает базовый CDC**: Производится "скольжение" окна по байтам файла, вычисляется хэш (отпечаток), и определяются "границы" чанков, где хэш соответствует условию (например, заканчивается нулями).
 | 
			
		||||
3. **Параметры FastCDC**:
 | 
			
		||||
   - `minSize = 100`, `normalSize = 200`, `maxSize = 500` — те же, что в предыдущем коде, подходят для текста длиной 7471 байт, обеспечивая около 30–40 чанков (7471 / 200 ≈ 37).
 | 
			
		||||
   - Маски: `maskS = 0b11111111` (8 бит, вероятность cut-point 1/256), `maskL = 0b1111` (4 бита, вероятность 1/16), уровень нормализации 2 (±2 бита от базовой log2(200) ≈ 8).
 | 
			
		||||
 | 
			
		||||
FastCDC является оптимизированной версией Gear-based CDC. Gear применяет быстрый хэш, но работает медленнее из-за частых проверок и неравномерных чанков. FastCDC ускоряет процесс в 3–5 раз, сохраняя высокую эффективность.
 | 
			
		||||
4. **Функция `fastcdc`**:
 | 
			
		||||
   - Сигнатура упрощена: удалён параметр `gear`, так как таблица теперь глобальная и статическая (`immutable gear`).
 | 
			
		||||
   - Логика осталась без изменений: инициализация хэша `fp` для первых `minSize` байт без проверок, фаза `maskS` до `normalSize`, затем `maskL` до `maxSize` или cut-point (`fp & mask == 0`).
 | 
			
		||||
 | 
			
		||||
### Шаг 2: Три Ключевые "Фишки" FastCDC
 | 
			
		||||
FastCDC использует три основных улучшения для достижения скорости и эффективности:
 | 
			
		||||
5. **Вывод**:
 | 
			
		||||
   - Код выводит общую длину текста, номер чанка, его размер и первые 50 символов содержимого (или всё, если короче).
 | 
			
		||||
   - Пример вывода (зависит от Gear-таблицы):
 | 
			
		||||
     ```
 | 
			
		||||
     Total length (bytes): 7471
 | 
			
		||||
     Chunk sizes and contents:
 | 
			
		||||
     Chunk 1: 239 bytes
 | 
			
		||||
     Content: Цель науки о данных — улучшить процесс принятия решений, о...
 | 
			
		||||
     
 | 
			
		||||
1. **Оптимизация Проверки Хэша (Simplified and Enhanced Hash Judgment)**:
 | 
			
		||||
   - В традиционных методах проверка хэша сложна (например, через деление modulo). FastCDC упрощает процесс с помощью побитового AND (&) с маской (фильтром из бит).
 | 
			
		||||
   - Аналогия: Вместо проверки делимости числа на 100 используется проверка на окончание двумя нулями — это быстрее для вычислений.
 | 
			
		||||
   - Дополнительно: Расширяется "окно" хэша до 48 бит с нулями в маске, что обеспечивает равномерность чанков и снижает коллизии позиций.
 | 
			
		||||
     Chunk 2: 204 bytes
 | 
			
		||||
     Content: сновывая их на более глубоком понимании ситуации с помощью ...
 | 
			
		||||
     
 | 
			
		||||
2. **Пропуск Границ для Маленьких Чанков (Sub-Minimum Chunk Cut-Point Skipping)**:
 | 
			
		||||
   - Если потенциальный чанк меньше минимального порога (например, 2 КБ), возможная граница игнорируется, и вычисление хэша продолжается.
 | 
			
		||||
   - Аналогия: При чтении книги конец абзаца пропускается, если абзац короче двух страниц, чтобы избежать траты времени на мелкие фрагменты.
 | 
			
		||||
   - Это ускоряет алгоритм, хотя может слегка снизить эффективность дедупликации (на 15%), делая некоторые чанки крупнее.
 | 
			
		||||
     Chunk 3: 211 bytes
 | 
			
		||||
     Content: анализа больших наборов данных. Как область деятельности на...
 | 
			
		||||
     ...
 | 
			
		||||
     Chunk 31: 65 bytes
 | 
			
		||||
     Content: определить, будет ли данный конкретный проект успешным.
 | 
			
		||||
     ```
 | 
			
		||||
 | 
			
		||||
3. **Нормализация Размеров Чанков (Normalized Chunking)**:
 | 
			
		||||
   - Для компенсации пропусков применяются разные маски: "строгая" (MaskS, с большим количеством '1' бит — реже границы) для маленьких чанков и "слабая" (MaskL, с меньшим количеством '1' бит — чаще границы) для больших.
 | 
			
		||||
   - Аналогия: Для маленького чанка фильтр ужесточается, чтобы позволить рост; для большого — ослабляется, чтобы timely разрезать. В результате чанки группируются вокруг идеального размера (например, 8 КБ), с распределением близким к нормальному.
 | 
			
		||||
   - Уровень нормализации: 1–3 (количество бит для изменения в масках).
 | 
			
		||||
 | 
			
		||||
### Шаг 3: Параметры — Что Настраивать?
 | 
			
		||||
- **Ожидаемый размер чанка (Expected/Average Size, avg)**: Обычно 8 КБ (8192 байт). Это целевой размер для нормализации.
 | 
			
		||||
- **Минимальный размер (MinSize)**: 2 КБ, 4 КБ или 8 КБ. Ниже этого порога проверки пропускаются.
 | 
			
		||||
- **Максимальный размер (MaxSize)**: 64 КБ. При достижении происходит принудительное разбиение.
 | 
			
		||||
- **Маски (Masks)**: Битовые фильтры. Пример для avg=8 КБ:
 | 
			
		||||
  - MaskS (строгая): 15 бит '1' (реже срабатывает).
 | 
			
		||||
  - MaskA (базовая): 13 бит '1'.
 | 
			
		||||
  - MaskL (слабая): 11 бит '1'.
 | 
			
		||||
- **Gear-таблица**: Массив из 256 случайных 64-битных чисел для хэша.
 | 
			
		||||
 | 
			
		||||
### Шаг 4: Как Работает Алгоритм — Пошагово
 | 
			
		||||
1. **Подготовка**: Взять буфер данных (файл как массив байт, длиной n). Установить параметры. Инициализировать хэш (fp = 0) и позицию (i = MinSize — для пропуска мелких фрагментов).
 | 
			
		||||
 | 
			
		||||
2. **Проверка краёв**: Если n ≤ MinSize — весь буфер считается одним чанком. Если n > MaxSize — обрезать до MaxSize. Если n < avg — установить avg = n.
 | 
			
		||||
 | 
			
		||||
3. **Цикл для маленьких чанков (до avg, строгая маска)**:
 | 
			
		||||
   - Для каждой позиции i от MinSize до avg-1:
 | 
			
		||||
     - Обновить хэш: fp = (fp << 1) + Gear[src[i]] (сдвиг влево плюс значение из таблицы).
 | 
			
		||||
     - Проверить: если (fp & MaskS) == 0 — найдена граница. Вернуть i как размер чанка.
 | 
			
		||||
   - Это способствует росту чанков, снижая частоту разбиения.
 | 
			
		||||
 | 
			
		||||
4. **Цикл для больших чанков (от avg до MaxSize, слабая маска)**:
 | 
			
		||||
   - Для i от avg до MaxSize-1:
 | 
			
		||||
     - Обновить хэш аналогично.
 | 
			
		||||
     - Проверить: если (fp & MaskL) == 0 — найдена граница. Вернуть i.
 | 
			
		||||
   - Это увеличивает частоту разбиения, предотвращая перерост.
 | 
			
		||||
 | 
			
		||||
5. **Если граница не найдена**: Вернуть MaxSize (принудительная граница).
 | 
			
		||||
 | 
			
		||||
6. **Повтор**: Применить цикл к оставшимся данным до конца файла.
 | 
			
		||||
 | 
			
		||||
```mermaid
 | 
			
		||||
flowchart TD
 | 
			
		||||
    A("Начало: Входные данные, min/avg/max размеры, маски S/L") --> B{"Достигнут конец данных?"}
 | 
			
		||||
    B -- Да --> Z("Конец: Последний чанк")
 | 
			
		||||
    B -- Нет --> C("Инициализировать hash fp=0, позиция i=min")
 | 
			
		||||
    C --> D{"Чанк < min?"}
 | 
			
		||||
    D -- Да --> E("Пропустить проверку: i++, обновить hash fp = (fp << 1) + Gear[byte]")
 | 
			
		||||
    E --> D
 | 
			
		||||
    D -- Нет --> F{"Чанк < avg?"}
 | 
			
		||||
    F -- Да --> G("Проверить cut-point: fp & MaskS == 0?")
 | 
			
		||||
    G -- Да --> H("Разбить чанк на i, начать новый")
 | 
			
		||||
    G -- Нет --> I("i++, обновить hash")
 | 
			
		||||
    I --> F
 | 
			
		||||
    F -- Нет --> J("Проверить cut-point: fp & MaskL == 0?")
 | 
			
		||||
    J -- Да --> H
 | 
			
		||||
    J -- Нет --> K("i++, обновить hash")
 | 
			
		||||
    K --> L{"Чанк >= max?"}
 | 
			
		||||
    L -- Да --> H
 | 
			
		||||
    L -- Нет --> F
 | 
			
		||||
    H --> B
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Шаг 5: Псевдокод — В Стиле Языка D
 | 
			
		||||
Ниже приведен псевдокод в стиле языка программирования D. Предполагается, что Gear — это готовый массив ulong[256].
 | 
			
		||||
 | 
			
		||||
```d
 | 
			
		||||
ulong fastcdc(ubyte[] src, size_t n, size_t minSize, size_t maxSize, size_t normalSize, ulong maskS, ulong maskL, ulong[256] gear) {
 | 
			
		||||
    if (n <= minSize) return n;
 | 
			
		||||
    if (n > maxSize) n = maxSize;
 | 
			
		||||
    if (n < normalSize) normalSize = n;
 | 
			
		||||
 | 
			
		||||
    ulong fp = 0;
 | 
			
		||||
    size_t i = minSize;  // Пропуск мелких
 | 
			
		||||
 | 
			
		||||
    // Цикл до normalSize (строгая маска)
 | 
			
		||||
    while (i < normalSize) {
 | 
			
		||||
        fp = (fp << 1) + gear[src[i]];
 | 
			
		||||
        if ((fp & maskS) == 0) {
 | 
			
		||||
            return i;
 | 
			
		||||
        }
 | 
			
		||||
        i++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Цикл после (слабая маска)
 | 
			
		||||
    while (i < n) {
 | 
			
		||||
        fp = (fp << 1) + gear[src[i]];
 | 
			
		||||
        if ((fp & maskL) == 0) {
 | 
			
		||||
            return i;
 | 
			
		||||
        }
 | 
			
		||||
        i++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return n;  // Достигнут max
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Этот псевдокод можно использовать в цикле для обработки всего файла, собирая чанки.
 | 
			
		||||
 | 
			
		||||
### Шаг 6: Пример в Действии
 | 
			
		||||
Предположим файл размером 20 КБ, avg=8 КБ, min=2 КБ, max=64 КБ.
 | 
			
		||||
- Старт с i=2 КБ.
 | 
			
		||||
- Вычисление хэша до 8 КБ с MaskS — без срабатывания, продолжение.
 | 
			
		||||
- С 8 КБ применение MaskL — срабатывание на 10 КБ. Первый чанк: 10 КБ.
 | 
			
		||||
- Повтор для оставшихся 10 КБ.
 | 
			
		||||
Результат: Чанки примерно 8–16 КБ, с быстрыми вычислениями.
 | 
			
		||||
 | 
			
		||||
### Шаг 7: Преимущества и Когда Использовать
 | 
			
		||||
- **Плюсы**: В 3 раза быстрее Gear, в 10 раз быстрее Rabin; коэффициент дедупликации близок к оптимальному (падение менее 15%).
 | 
			
		||||
- **Минусы**: Эффективность зависит от данных; пропуски могут снижать производительность для очень похожих файлов.
 | 
			
		||||
- **Применение**: В системах типа IPFS, резервных копиях (Duplicati), хранилищах (S3 с дедупликацией).
 | 
			
		||||
6. **Особенности работы**:
 | 
			
		||||
   - **Rolling hash**: Для каждого байта `fp = (fp << 1) + gear[byte]`. Статическая таблица `gear` обеспечивает одинаковые результаты при каждом запуске.
 | 
			
		||||
   - **Нормализация**: `maskS` делает cut-points реже до 200 байт, `maskL` — чаще после, поддерживая чанки около 200 байт.
 | 
			
		||||
   - **UTF-8**: Границы чанков байтовые, могут не совпадать с границами слов из-за кириллицы (2 байта на символ).
 | 
			
		||||
   - **Дедупликация**: Для проверки дубликатов можно добавить хэширование чанков (например, SHA-256), но это не включено в текущий код.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										3
									
								
								dub.json
									
										
									
									
									
								
							
							
						
						
									
										3
									
								
								dub.json
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -7,5 +7,6 @@
 | 
			
		|||
	"license": "BSL-1.0",
 | 
			
		||||
	"name": "cdc",
 | 
			
		||||
	"targetPath": "bin",
 | 
			
		||||
	"targetType": "executable"
 | 
			
		||||
	"targetType": "executable",
 | 
			
		||||
	"dflags": ["-J=source"]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										457
									
								
								source/app.d
									
										
									
									
									
								
							
							
						
						
									
										457
									
								
								source/app.d
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,409 +1,80 @@
 | 
			
		|||
module app;
 | 
			
		||||
import std.stdio;
 | 
			
		||||
 | 
			
		||||
import std.stdio : writeln, writefln, File;
 | 
			
		||||
import std.file : exists, mkdirRecurse, read, write, readText, rename;
 | 
			
		||||
import std.path : baseName, dirName, buildPath, absolutePath;
 | 
			
		||||
import std.getopt : getopt;
 | 
			
		||||
import std.string : strip, split, splitLines;
 | 
			
		||||
import std.algorithm.searching : startsWith, endsWith;
 | 
			
		||||
import std.conv : to;
 | 
			
		||||
import std.datetime : Clock;
 | 
			
		||||
import std.exception : enforce;
 | 
			
		||||
import std.digest.sha : sha256Of, SHA256;
 | 
			
		||||
import std.format : format;
 | 
			
		||||
// Статическая таблица Gear (256 случайных 64-битных чисел)
 | 
			
		||||
mixin(import("gear.d"));
 | 
			
		||||
 | 
			
		||||
import fastcdc; // твой модуль FastCDC
 | 
			
		||||
 | 
			
		||||
// ---------- утилиты ----------
 | 
			
		||||
 | 
			
		||||
// У Phobos read(...) на некоторых версиях возвращает void[].
 | 
			
		||||
// Безопасно приводим к ubyte[] в @trusted-обёртке.
 | 
			
		||||
@trusted ubyte[] readBytes(string path)
 | 
			
		||||
ulong fastcdc(const ubyte[] src, size_t n, size_t minSize, size_t maxSize, size_t normalSize, ulong maskS, ulong maskL)
 | 
			
		||||
{
 | 
			
		||||
	auto v = read(path); // void[]
 | 
			
		||||
	return cast(ubyte[]) v; // это новый буфер байт → безопасно
 | 
			
		||||
}
 | 
			
		||||
	if (n <= minSize)
 | 
			
		||||
		return n;
 | 
			
		||||
	if (n > maxSize)
 | 
			
		||||
		n = maxSize;
 | 
			
		||||
	if (n < normalSize)
 | 
			
		||||
		normalSize = n;
 | 
			
		||||
 | 
			
		||||
// hex из байтов (scope для локальных срезов)
 | 
			
		||||
@safe pure
 | 
			
		||||
string toHex(scope const(ubyte)[] bytes)
 | 
			
		||||
{
 | 
			
		||||
	immutable char[16] HEX = "0123456789abcdef";
 | 
			
		||||
	auto buf = new char[bytes.length * 2];
 | 
			
		||||
	size_t j = 0;
 | 
			
		||||
	foreach (b; bytes)
 | 
			
		||||
	{
 | 
			
		||||
		buf[j++] = HEX[(b >> 4) & 0xF];
 | 
			
		||||
		buf[j++] = HEX[b & 0xF];
 | 
			
		||||
	}
 | 
			
		||||
	return buf.idup;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Путь чанка с fanout: store/chunks/aa/bb/<hash>.bin
 | 
			
		||||
@safe
 | 
			
		||||
string chunkPath(string storeDir, string hashHex)
 | 
			
		||||
{
 | 
			
		||||
	auto a = hashHex[0 .. 2];
 | 
			
		||||
	auto b = hashHex[2 .. 4];
 | 
			
		||||
	return buildPath(storeDir, "chunks", a, b, hashHex ~ ".bin");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Путь манифеста: store/manifests/<name>.<epoch>.manifest
 | 
			
		||||
@safe
 | 
			
		||||
string manifestPath(string storeDir, string srcPath, long epoch)
 | 
			
		||||
{
 | 
			
		||||
	auto name = baseName(srcPath);
 | 
			
		||||
	return buildPath(storeDir, "manifests", name ~ "." ~ to!string(epoch) ~ ".manifest");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Обновить/создать "указатель" на последний манифест:
 | 
			
		||||
// store/manifests/<name>.latest  (содержит basename файла манифеста)
 | 
			
		||||
@safe
 | 
			
		||||
void writeLatestPointer(string storeDir, string srcPath, string manifestFullPath)
 | 
			
		||||
{
 | 
			
		||||
	auto manifestsDir = buildPath(storeDir, "manifests");
 | 
			
		||||
	mkdirRecurse(manifestsDir);
 | 
			
		||||
	auto latestFile = buildPath(manifestsDir, baseName(srcPath) ~ ".latest");
 | 
			
		||||
	auto justName = baseName(manifestFullPath);
 | 
			
		||||
	write(latestFile, justName); // plain text
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Разрешить путь манифеста: если мне дали *.latest — прочитать ссылку.
 | 
			
		||||
@safe
 | 
			
		||||
string resolveManifestPath(string storeDir, string manifestGiven)
 | 
			
		||||
{
 | 
			
		||||
	if (manifestGiven.endsWith(".latest") && exists(manifestGiven))
 | 
			
		||||
	{
 | 
			
		||||
		auto dir = dirName(manifestGiven);
 | 
			
		||||
		auto name = readText(manifestGiven).strip;
 | 
			
		||||
		// если в файле относительный basename — склеиваем с текущей директорией
 | 
			
		||||
		return buildPath(dir, name);
 | 
			
		||||
	}
 | 
			
		||||
	// Возможно, дали просто store/manifests/<name>.latest (существующий файл)
 | 
			
		||||
	if (manifestGiven.endsWith(".latest") && exists(buildPath(storeDir, "manifests", baseName(
 | 
			
		||||
			manifestGiven))))
 | 
			
		||||
	{
 | 
			
		||||
		auto latest = buildPath(storeDir, "manifests", baseName(manifestGiven));
 | 
			
		||||
		auto name = readText(latest).strip;
 | 
			
		||||
		return buildPath(dirName(latest), name);
 | 
			
		||||
	}
 | 
			
		||||
	return manifestGiven;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Атомарная запись чанка: через временный файл и rename()
 | 
			
		||||
@trusted
 | 
			
		||||
void writeAtomic(string path, in ubyte[] data)
 | 
			
		||||
{
 | 
			
		||||
	auto tmp = path ~ ".tmp";
 | 
			
		||||
	auto f = File(tmp, "wb");
 | 
			
		||||
	f.rawWrite(data);
 | 
			
		||||
	f.flush();
 | 
			
		||||
	f.close();
 | 
			
		||||
	// ensure конечные директории существуют (на случай гонки)
 | 
			
		||||
	mkdirRecurse(dirName(path));
 | 
			
		||||
	rename(tmp, path); // POSIX: атомарно
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Создать служебные директории
 | 
			
		||||
@safe
 | 
			
		||||
void ensureDirs(string storeDir)
 | 
			
		||||
{
 | 
			
		||||
	mkdirRecurse(buildPath(storeDir, "chunks"));
 | 
			
		||||
	mkdirRecurse(buildPath(storeDir, "manifests"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Вспомогалка: записать строку в файл И одновременно накормить хэшер
 | 
			
		||||
@trusted
 | 
			
		||||
void mfWriteLine(ref File mf, ref SHA256 h, string line)
 | 
			
		||||
{
 | 
			
		||||
	mf.writeln(line);
 | 
			
		||||
	h.put(cast(const(ubyte)[]) line);
 | 
			
		||||
	h.put(cast(const(ubyte)[]) "\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ---------- split ----------
 | 
			
		||||
 | 
			
		||||
struct SplitOpts
 | 
			
		||||
{
 | 
			
		||||
	string storeDir;
 | 
			
		||||
	string filePath;
 | 
			
		||||
	size_t minSize = 8 * 1024;
 | 
			
		||||
	size_t avgSize = 64 * 1024;
 | 
			
		||||
	size_t maxSize = 256 * 1024;
 | 
			
		||||
	size_t logEvery = 256; // каждые N чанков логировать (0 = выкл)
 | 
			
		||||
	string profile; // "text" | "bin" | ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@safe
 | 
			
		||||
void applyProfile(ref SplitOpts opt)
 | 
			
		||||
{
 | 
			
		||||
	if (opt.profile == "text")
 | 
			
		||||
	{
 | 
			
		||||
		if (opt.minSize == 8 * 1024)
 | 
			
		||||
			opt.minSize = 4 * 1024;
 | 
			
		||||
		if (opt.avgSize == 64 * 1024)
 | 
			
		||||
			opt.avgSize = 32 * 1024;
 | 
			
		||||
		if (opt.maxSize == 256 * 1024)
 | 
			
		||||
			opt.maxSize = 128 * 1024;
 | 
			
		||||
	}
 | 
			
		||||
	else if (opt.profile == "bin")
 | 
			
		||||
	{
 | 
			
		||||
		if (opt.minSize == 8 * 1024)
 | 
			
		||||
			opt.minSize = 16 * 1024;
 | 
			
		||||
		if (opt.avgSize == 64 * 1024)
 | 
			
		||||
			opt.avgSize = 128 * 1024;
 | 
			
		||||
		if (opt.maxSize == 256 * 1024)
 | 
			
		||||
			opt.maxSize = 512 * 1024;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@safe
 | 
			
		||||
int cmdSplit(SplitOpts opt)
 | 
			
		||||
{
 | 
			
		||||
	enforce(exists(opt.filePath), "Файл не найден: " ~ opt.filePath);
 | 
			
		||||
	ensureDirs(opt.storeDir);
 | 
			
		||||
	applyProfile(opt);
 | 
			
		||||
 | 
			
		||||
	// бинарное чтение исходника
 | 
			
		||||
	ubyte[] data = readBytes(opt.filePath);
 | 
			
		||||
 | 
			
		||||
	FastCDCParams p = {opt.minSize, opt.avgSize, opt.maxSize};
 | 
			
		||||
	p.normalize();
 | 
			
		||||
 | 
			
		||||
	size_t chunkCount = 0;
 | 
			
		||||
	size_t totalBytes = data.length;
 | 
			
		||||
 | 
			
		||||
	// имя манифеста
 | 
			
		||||
	auto epoch = Clock.currTime().toUnixTime();
 | 
			
		||||
	auto mfPath = manifestPath(opt.storeDir, opt.filePath, epoch);
 | 
			
		||||
	auto mfDir = buildPath(opt.storeDir, "manifests");
 | 
			
		||||
	mkdirRecurse(mfDir);
 | 
			
		||||
	auto mf = File(mfPath, "w");
 | 
			
		||||
 | 
			
		||||
	// будем сразу считать SHA-256 манифеста (кроме финальной строки manifest_sha256)
 | 
			
		||||
	SHA256 mh;
 | 
			
		||||
 | 
			
		||||
	// шапка манифеста
 | 
			
		||||
	mfWriteLine(mf, mh, "# FastCDC manifest");
 | 
			
		||||
	mfWriteLine(mf, mh, "path\t" ~ absolutePath(opt.filePath));
 | 
			
		||||
	mfWriteLine(mf, mh, format("size\t%s", to!string(totalBytes)));
 | 
			
		||||
	mfWriteLine(mf, mh, "algo\tsha256");
 | 
			
		||||
	mfWriteLine(mf, mh, format("min\t%u", cast(uint) p.minSize));
 | 
			
		||||
	mfWriteLine(mf, mh, format("avg\t%u", cast(uint) p.avgSize));
 | 
			
		||||
	mfWriteLine(mf, mh, format("max\t%u", cast(uint) p.maxSize));
 | 
			
		||||
	mfWriteLine(mf, mh, "ord\thash\tsize");
 | 
			
		||||
 | 
			
		||||
	// потоковая нарезка: sha256 чанка, атомарная запись, строка в манифест
 | 
			
		||||
	size_t ord = 0;
 | 
			
		||||
	processStream(data, p, (size_t start, size_t len) @safe {
 | 
			
		||||
		auto slice = data[start .. start + len];
 | 
			
		||||
		auto digest = sha256Of(slice); // ubyte[32]
 | 
			
		||||
		auto hex = toHex(digest[]);
 | 
			
		||||
 | 
			
		||||
		auto cpath = chunkPath(opt.storeDir, hex);
 | 
			
		||||
 | 
			
		||||
		// подготовим подпапки aa/bb
 | 
			
		||||
		mkdirRecurse(buildPath(opt.storeDir, "chunks", hex[0 .. 2]));
 | 
			
		||||
		mkdirRecurse(buildPath(opt.storeDir, "chunks", hex[0 .. 2], hex[2 .. 4]));
 | 
			
		||||
 | 
			
		||||
		if (!exists(cpath))
 | 
			
		||||
			writeAtomic(cpath, slice); // атомарная запись
 | 
			
		||||
 | 
			
		||||
		// строка манифеста для чанка
 | 
			
		||||
		auto line = format("%u\t%s\t%u", cast(uint) ord, hex, cast(uint) len);
 | 
			
		||||
		mfWriteLine(mf, mh, line);
 | 
			
		||||
 | 
			
		||||
		++ord;
 | 
			
		||||
		++chunkCount;
 | 
			
		||||
		if (opt.logEvery != 0 && (ord % opt.logEvery) == 0)
 | 
			
		||||
			writefln("… %u chunks", cast(uint) ord);
 | 
			
		||||
 | 
			
		||||
		return 0; // продолжать
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// финализируем хэш манифеста (без строки manifest_sha256) и добавляем контрольную строку
 | 
			
		||||
	auto manifestDigest = mh.finish(); // ubyte[32]
 | 
			
		||||
	auto manifestHex = toHex(manifestDigest[]);
 | 
			
		||||
	mf.writeln("manifest_sha256\t" ~ manifestHex);
 | 
			
		||||
 | 
			
		||||
	mf.flush();
 | 
			
		||||
	mf.close();
 | 
			
		||||
 | 
			
		||||
	// запишем указатель "последний манифест"
 | 
			
		||||
	writeLatestPointer(opt.storeDir, opt.filePath, mfPath);
 | 
			
		||||
 | 
			
		||||
	writefln("split: %s", opt.filePath);
 | 
			
		||||
	writefln("store: %s", opt.storeDir);
 | 
			
		||||
	writefln("manifest: %s", mfPath);
 | 
			
		||||
	writefln("chunks: %u, bytes: %u", cast(uint) chunkCount, cast(uint) totalBytes);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ---------- restore ----------
 | 
			
		||||
 | 
			
		||||
struct RestoreOpts
 | 
			
		||||
{
 | 
			
		||||
	string storeDir;
 | 
			
		||||
	string manifestFile; // может быть *.latest
 | 
			
		||||
	string outFile;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@safe
 | 
			
		||||
int cmdRestore(RestoreOpts opt)
 | 
			
		||||
{
 | 
			
		||||
	auto realManifest = resolveManifestPath(opt.storeDir, opt.manifestFile);
 | 
			
		||||
	enforce(exists(realManifest), "Манифест не найден: " ~ realManifest);
 | 
			
		||||
 | 
			
		||||
	string text = readText(realManifest);
 | 
			
		||||
	auto lines = splitLines(text);
 | 
			
		||||
 | 
			
		||||
	// 1) Проверка целостности: пересчитать SHA-256 всех строк ДО manifest_sha256
 | 
			
		||||
	SHA256 mh;
 | 
			
		||||
	string expectedHex;
 | 
			
		||||
	foreach (ln; lines)
 | 
			
		||||
	{
 | 
			
		||||
		auto s = ln.strip;
 | 
			
		||||
		if (s.startsWith("manifest_sha256"))
 | 
			
		||||
		{
 | 
			
		||||
			auto cols = s.split('\t');
 | 
			
		||||
			enforce(cols.length == 2, "Повреждённая строка manifest_sha256");
 | 
			
		||||
			expectedHex = cols[1];
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
		// включаем в хэш строку и символ перевода строки
 | 
			
		||||
		// (в split() выше переводы строк уже отрезаны)
 | 
			
		||||
		mh.put(cast(const(ubyte)[]) ln);
 | 
			
		||||
		mh.put(cast(const(ubyte)[]) "\n");
 | 
			
		||||
	}
 | 
			
		||||
	if (expectedHex.length)
 | 
			
		||||
	{
 | 
			
		||||
		auto gotHex = toHex(mh.finish()[]);
 | 
			
		||||
		enforce(gotHex == expectedHex,
 | 
			
		||||
			"Контрольная сумма манифеста не совпала:\n  ожидалось: " ~ expectedHex ~ "\n  получено:  " ~ gotHex);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 2) Найти секцию данных "ord\thash\tsize"
 | 
			
		||||
	ulong fp = 0;
 | 
			
		||||
	size_t i = 0;
 | 
			
		||||
	while (i < lines.length && !lines[i].strip.startsWith("ord"))
 | 
			
		||||
		++i;
 | 
			
		||||
	enforce(i < lines.length, "Не найден заголовок секции данных в манифесте");
 | 
			
		||||
	++i;
 | 
			
		||||
 | 
			
		||||
	auto dst = File(opt.outFile, "wb");
 | 
			
		||||
 | 
			
		||||
	size_t count = 0;
 | 
			
		||||
	for (; i < lines.length; ++i)
 | 
			
		||||
	// Инициализация fp для первых minSize байт (без проверки cut-point)
 | 
			
		||||
	while (i < minSize)
 | 
			
		||||
	{
 | 
			
		||||
		auto ln = lines[i].strip;
 | 
			
		||||
		if (ln.length == 0 || ln[0] == '#' || ln.startsWith("manifest_sha256"))
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		auto cols = ln.split('\t');
 | 
			
		||||
		enforce(cols.length == 3, "Строка манифеста повреждена: " ~ ln);
 | 
			
		||||
 | 
			
		||||
		auto hashHex = cols[1];
 | 
			
		||||
		auto cpath = chunkPath(opt.storeDir, hashHex);
 | 
			
		||||
		enforce(exists(cpath), "Чанк не найден: " ~ cpath);
 | 
			
		||||
 | 
			
		||||
		ubyte[] chunkData = readBytes(cpath);
 | 
			
		||||
		dst.rawWrite(chunkData);
 | 
			
		||||
		++count;
 | 
			
		||||
		fp = (fp << 1) + gear[src[i]];
 | 
			
		||||
		i++;
 | 
			
		||||
	}
 | 
			
		||||
	// Цикл до normalSize (строгая маска)
 | 
			
		||||
	while (i < normalSize)
 | 
			
		||||
	{
 | 
			
		||||
		fp = (fp << 1) + gear[src[i]];
 | 
			
		||||
		if ((fp & maskS) == 0)
 | 
			
		||||
		{
 | 
			
		||||
			return i;
 | 
			
		||||
		}
 | 
			
		||||
		i++;
 | 
			
		||||
	}
 | 
			
		||||
	// Цикл после (слабая маска)
 | 
			
		||||
	while (i < n)
 | 
			
		||||
	{
 | 
			
		||||
		fp = (fp << 1) + gear[src[i]];
 | 
			
		||||
		if ((fp & maskL) == 0)
 | 
			
		||||
		{
 | 
			
		||||
			return i;
 | 
			
		||||
		}
 | 
			
		||||
		i++;
 | 
			
		||||
 | 
			
		||||
	dst.close();
 | 
			
		||||
	writefln("restore: %s <- %s (chunks: %u)", opt.outFile, realManifest, cast(uint) count);
 | 
			
		||||
	return 0;
 | 
			
		||||
	}
 | 
			
		||||
	return n; // Достигнут max
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ---------- CLI ----------
 | 
			
		||||
 | 
			
		||||
@safe
 | 
			
		||||
void printHelp(string prog)
 | 
			
		||||
void main()
 | 
			
		||||
{
 | 
			
		||||
	writeln("Usage:");
 | 
			
		||||
	writeln("  ",
 | 
			
		||||
		prog, " split   --store <dir> <file> [--profile text|bin] [--min N] [--avg N] [--max N] [--log-every N]");
 | 
			
		||||
	writeln("  ", prog, " restore --store <dir> <manifest|*.latest> <out_file>");
 | 
			
		||||
}
 | 
			
		||||
	// Текст
 | 
			
		||||
	mixin(import("text.d"));
 | 
			
		||||
 | 
			
		||||
int main(string[] args)
 | 
			
		||||
{ // без @safe: getopt берёт &var
 | 
			
		||||
	if (args.length < 2)
 | 
			
		||||
	// Конвертация в ubyte[] (UTF-8)
 | 
			
		||||
	ubyte[] data = cast(ubyte[]) text;
 | 
			
		||||
	size_t totalLength = data.length;
 | 
			
		||||
	writefln("Общая длина текста (в байтах): %d", totalLength);
 | 
			
		||||
 | 
			
		||||
	// Параметры FastCDC
 | 
			
		||||
	size_t minSize = 100;
 | 
			
		||||
	size_t normalSize = 200;
 | 
			
		||||
	size_t maxSize = 500;
 | 
			
		||||
 | 
			
		||||
	ulong maskS = 0b1111_1111;
 | 
			
		||||
	ulong maskL = 0b1111;
 | 
			
		||||
 | 
			
		||||
	size_t offset = 0;
 | 
			
		||||
	size_t chunkNumber = 1;
 | 
			
		||||
 | 
			
		||||
	writeln("Размеры и содержимое чанков:");
 | 
			
		||||
	while (offset < totalLength)
 | 
			
		||||
	{
 | 
			
		||||
		printHelp(args[0]);
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch (args[1])
 | 
			
		||||
	{
 | 
			
		||||
	case "split":
 | 
			
		||||
		{
 | 
			
		||||
			SplitOpts opt;
 | 
			
		||||
			string store;
 | 
			
		||||
			string profile;
 | 
			
		||||
			size_t minS = 0, avgS = 0, maxS = 0, logEvery = 256;
 | 
			
		||||
 | 
			
		||||
			auto res = getopt(args,
 | 
			
		||||
				"store", &store,
 | 
			
		||||
				"profile", &profile,
 | 
			
		||||
				"min", &minS,
 | 
			
		||||
				"avg", &avgS,
 | 
			
		||||
				"max", &maxS,
 | 
			
		||||
				"log-every", &logEvery
 | 
			
		||||
			);
 | 
			
		||||
			if (res.helpWanted)
 | 
			
		||||
			{
 | 
			
		||||
				printHelp(args[0]);
 | 
			
		||||
				return 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (args.length < 3 || store.length == 0)
 | 
			
		||||
			{
 | 
			
		||||
				printHelp(args[0]);
 | 
			
		||||
				return 1;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			opt.storeDir = store;
 | 
			
		||||
			opt.filePath = args[2];
 | 
			
		||||
			opt.profile = profile;
 | 
			
		||||
			if (minS)
 | 
			
		||||
				opt.minSize = minS;
 | 
			
		||||
			if (avgS)
 | 
			
		||||
				opt.avgSize = avgS;
 | 
			
		||||
			if (maxS)
 | 
			
		||||
				opt.maxSize = maxS;
 | 
			
		||||
			opt.logEvery = logEvery;
 | 
			
		||||
 | 
			
		||||
			return cmdSplit(opt);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case "restore":
 | 
			
		||||
		{
 | 
			
		||||
			RestoreOpts opt;
 | 
			
		||||
			string store;
 | 
			
		||||
 | 
			
		||||
			auto res = getopt(args, "store", &store);
 | 
			
		||||
			if (res.helpWanted)
 | 
			
		||||
			{
 | 
			
		||||
				printHelp(args[0]);
 | 
			
		||||
				return 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (args.length < 4 || store.length == 0)
 | 
			
		||||
			{
 | 
			
		||||
				printHelp(args[0]);
 | 
			
		||||
				return 1;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			opt.storeDir = store;
 | 
			
		||||
			opt.manifestFile = args[2]; // можно передать *.latest
 | 
			
		||||
			opt.outFile = args[3];
 | 
			
		||||
 | 
			
		||||
			return cmdRestore(opt);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		printHelp(args[0]);
 | 
			
		||||
		return 1;
 | 
			
		||||
		size_t remaining = totalLength - offset;
 | 
			
		||||
		size_t chunkSize = fastcdc(data[offset .. $], remaining, minSize, maxSize, normalSize, maskS, maskL);
 | 
			
		||||
		writefln("Чанк %d: %d байт", chunkNumber, chunkSize);
 | 
			
		||||
		string chunkContent = cast(string) data[offset .. offset + chunkSize];
 | 
			
		||||
		writefln("Содержимое: %s\n", chunkContent);
 | 
			
		||||
		offset += chunkSize;
 | 
			
		||||
		chunkNumber++;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										272
									
								
								source/fastcdc.d
									
										
									
									
									
								
							
							
						
						
									
										272
									
								
								source/fastcdc.d
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,272 +0,0 @@
 | 
			
		|||
module fastcdc;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * FastCDC (скользящее контент-зависимое разбиение).
 | 
			
		||||
 *
 | 
			
		||||
 * Идея: идём по данным байт за байтом, считаем rolling-hash (Gear),
 | 
			
		||||
 * и ставим границы чанков там, где hash удовлетворяет простому условию:
 | 
			
		||||
 *   hash & mask == 0
 | 
			
		||||
 *
 | 
			
		||||
 * При этом действуют три порога:
 | 
			
		||||
 *   - minSize: до этой длины чанк не разрываем (слишком мелко)
 | 
			
		||||
 *   - avgSize: «целевой» средний размер; после него границы ищем активнее
 | 
			
		||||
 *   - maxSize: жёсткий максимум; если так и не нашли границу — режем принудительно
 | 
			
		||||
 *
 | 
			
		||||
 * Благодаря контент-зависимым границам, маленькие вставки/удаления
 | 
			
		||||
 * меняют только локальные чанки; большинство остальных переиспользуется.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/// Параметры FastCDC. Рекомендуется avgSize как степень двойки (например, 64 KiB).
 | 
			
		||||
struct FastCDCParams
 | 
			
		||||
{
 | 
			
		||||
	/// Минимальная длина чанка: пока длина меньше — границы не ставим.
 | 
			
		||||
	size_t minSize = 8 * 1024;
 | 
			
		||||
 | 
			
		||||
	/// Целевой средний размер чанка (желательно 2^k: 32, 64, 128 KiB и т.п.).
 | 
			
		||||
	size_t avgSize = 64 * 1024;
 | 
			
		||||
 | 
			
		||||
	/// Жёсткий максимум длины чанка: достигли — режем независимо от хэша.
 | 
			
		||||
	size_t maxSize = 256 * 1024;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
     * Нормализация границ (делает параметры самосогласованными):
 | 
			
		||||
     *  - minSize >= 4 KiB
 | 
			
		||||
     *  - avgSize > minSize  (если нет — удваиваем)
 | 
			
		||||
     *  - maxSize > avgSize  (если нет — умножаем на 4)
 | 
			
		||||
     *
 | 
			
		||||
     * @safe nothrow @nogc — чтобы можно было вызывать из безопасных функций
 | 
			
		||||
     * без аллокаций/исключений.
 | 
			
		||||
     */
 | 
			
		||||
	@safe nothrow @nogc
 | 
			
		||||
	void normalize()
 | 
			
		||||
	{
 | 
			
		||||
		enum ki = 1024;
 | 
			
		||||
		if (minSize < 4 * ki)
 | 
			
		||||
			minSize = 4 * ki;
 | 
			
		||||
		if (avgSize <= minSize)
 | 
			
		||||
			avgSize = minSize * 2;
 | 
			
		||||
		if (maxSize <= avgSize)
 | 
			
		||||
			maxSize = avgSize * 4;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Диапазон чанка: [start .. start+len) в исходном буфере.
 | 
			
		||||
struct ChunkRange
 | 
			
		||||
{
 | 
			
		||||
	size_t start; // смещение начала чанка
 | 
			
		||||
	size_t len; // длина чанка в байтах
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Таблица для Gear-rolling-hash: 256 псевдослучайных 32-битных констант.
 | 
			
		||||
 * Хэш обновляется так: h = (h << 1) + GEAR[byte].
 | 
			
		||||
 * Константы фиксированы → детерминированные границы при одинаковых параметрах.
 | 
			
		||||
 */
 | 
			
		||||
private immutable uint[256] GEAR = [
 | 
			
		||||
	0x4f1bbcdc, 0xe47c2b1a, 0x1a16b407, 0xa88f9d43, 0x33b0b5c5, 0x0f0b1192,
 | 
			
		||||
	0xb1c2e5b8, 0xc6f5a2a9, 0x7d1ea04c, 0x26358f23, 0x8f9a7902, 0x5ab7d6ee,
 | 
			
		||||
	0x2f6d3a8c, 0x9e13c540, 0x4d7c8b99, 0xf3a1d2b1, 0x0b7d4c62, 0x81f2a5d3,
 | 
			
		||||
	0x19a8b604, 0x6cc7e190, 0x559e43a1, 0xd2f01937, 0x7a53cc4f, 0x0c1d5e66,
 | 
			
		||||
	0x3b7f6a22, 0x99d104b7, 0x4aa7f9e1, 0x2ce3b8c0, 0x6a8f73d4, 0xe1b2c9a8,
 | 
			
		||||
	0x57c04b13, 0xa4e91572, 0x13f7c2aa, 0x8b1c0f5e, 0x5e6a92c1, 0x0af41937,
 | 
			
		||||
	0x7fe0bd54, 0x26b3e71a, 0x942d6c83, 0x3c51a0ef, 0xd57f2b33, 0x61a4cc09,
 | 
			
		||||
	0x0d9b8a71, 0xb7e50f46, 0x48a3d1f0, 0x2f1e6cb2, 0x73cd98a5, 0xe92a13c9,
 | 
			
		||||
	0xa1c7f02e, 0x5b0e6a97, 0x0c8f2d31, 0xd1a47b66, 0x6fe3b920, 0x20b9d4a1,
 | 
			
		||||
	0x9a5c0f3d, 0x4e81a2c7, 0xf02b5934, 0x1bc7d8a2, 0x8e0a64f1, 0x37d4b20c,
 | 
			
		||||
	0x6c09f5d3, 0xa2391e84, 0x5f7ab0e2, 0x0b1c6d57, 0x7c3f9a15, 0x12ad54e3,
 | 
			
		||||
	0x8b6f0c2d, 0x45e1d7a9, 0x2af39b60, 0x9c07e4d1, 0x3d5a81b2, 0xe6c21458,
 | 
			
		||||
	0xd9a03f1c, 0x64b7e0a3, 0x0ea19c76, 0xb2d5480f, 0x49f3a7d5, 0x21c58e92,
 | 
			
		||||
	0x75ae39c1, 0xed1046ab, 0xa8c3f12d, 0x5c0e7b94, 0x0d8f2e31, 0xd4a57c66,
 | 
			
		||||
	0x6ee4ba20, 0x23bad5a1, 0x985d0f3d, 0x4f82a3c7, 0xf12c5a34, 0x1ac8d9a2,
 | 
			
		||||
	0x8f0b65f1, 0x36d5b30c, 0x6b0af6d3, 0xa33a1f84, 0x5e7bb1e2, 0x0a1d6e57,
 | 
			
		||||
	0x7d409b15, 0x11ae55e3, 0x8a700d2d, 0x44e2d8a9, 0x2bf49c60, 0x9d08e5d1,
 | 
			
		||||
	0x3c5b82b2, 0xe7c31558, 0xd8a1401c, 0x65b8e1a3, 0x0fa29d76, 0xb3d6490f,
 | 
			
		||||
	0x48f4a8d5, 0x22c68f92, 0x74af3ac1, 0xec1147ab, 0xa9c4f22d, 0x5d0f7c94,
 | 
			
		||||
	0x0e902f31, 0xd5a67d66, 0x6de5bb20, 0x24bbd6a1, 0x975e103d, 0x4e83a4c7,
 | 
			
		||||
	0xf22d5b34, 0x1bc9daa2, 0x8e0c66f1, 0x35d6b40c, 0x6a0bf7d3, 0xa23b2084,
 | 
			
		||||
	0x5f7cb2e2, 0x0b1e6f57, 0x7c419c15, 0x10af56e3, 0x8b710e2d, 0x45e3d9a9,
 | 
			
		||||
	0x2af59d60, 0x9c09e6d1, 0x3d5c83b2, 0xe6c41658, 0xd9a2411c, 0x64b9e2a3,
 | 
			
		||||
	0x0ea39e76, 0xb2d74a0f, 0x49f5a9d5, 0x23c79092, 0x75b03bc1, 0xed1248ab,
 | 
			
		||||
	0xa8c5f32d, 0x5c107d94, 0x0d913031, 0xd4a77e66, 0x6ee6bc20, 0x25bcd7a1,
 | 
			
		||||
	0x985f113d, 0x4f84a5c7, 0xf12e5c34, 0x1acadba2, 0x8f0d67f1, 0x36d7b50c,
 | 
			
		||||
	0x6b0cf8d3, 0xa33c2184, 0x5e7db3e2, 0x0a1f7057, 0x7d429d15, 0x11b057e3,
 | 
			
		||||
	0x8a720f2d, 0x44e4daa9, 0x2bf69e60, 0x9d0ae7d1, 0x3c5d84b2, 0xe7c51758,
 | 
			
		||||
	0xd8a3421c, 0x65bae3a3, 0x0fa49f76, 0xb3d84b0f, 0x48f6aad5, 0x22c89192,
 | 
			
		||||
	0x74b13cc1, 0xec1349ab, 0xa9c6f42d, 0x5d117e94, 0x0e923131, 0xd5a87f66,
 | 
			
		||||
	0x6de7bd20, 0x24bdd8a1, 0x9750123d, 0x4e85a6c7, 0xf22f5d34, 0x1bcbdca2,
 | 
			
		||||
	0x8e0e68f1, 0x35d8b60c, 0x6a0df9d3, 0xa23d2284, 0x5f7eb4e2, 0x0b207157,
 | 
			
		||||
	0x7c439e15, 0x10b158e3, 0x8b73102d, 0x45e5dba9, 0x2af79f60, 0x9c0be8d1,
 | 
			
		||||
	0x3d5e85b2, 0xe6c61858, 0xd9a4431c, 0x64bbe4a3, 0x0ea5a076, 0xb2d94c0f,
 | 
			
		||||
	0x49f7abd5, 0x23c99292, 0x75b23dc1, 0xed144aab, 0xa8c7f52d, 0x5c127f94,
 | 
			
		||||
	0x0d933231, 0xd4a98066, 0x6ee8be20, 0x25bed9a1, 0x9861133d, 0x4f86a7c7,
 | 
			
		||||
	0xf1305e34, 0x1acca0a2, 0x8f0f69f1, 0x36d9b70c, 0x6b0efaD3, 0xa33e2384,
 | 
			
		||||
	0x5e7fb5e2, 0x0a217257, 0x7d449f15, 0x11b259e3, 0x8a74112d, 0x44e6dca9,
 | 
			
		||||
	0x2bf8a060, 0x9d0ce9d1, 0x3c5f86b2, 0xe7c71958, 0xd8a5441c, 0x65bce5a3,
 | 
			
		||||
	0x0fa6a176, 0xb3da4d0f, 0x48f8acd5, 0x22ca9392, 0x74b33ec1, 0xec154bab
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
/// Обновление Gear-хэша одним байтом. @safe @nothrow @nogc — без аллокаций/исключений.
 | 
			
		||||
@safe nothrow @nogc
 | 
			
		||||
private uint gearUpdate(uint h, ubyte b)
 | 
			
		||||
{
 | 
			
		||||
	// Побитовый сдвиг + добавление таблицы → быстрый, но «живой» rolling-hash.
 | 
			
		||||
	return (h << 1) + GEAR[b];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Ближайшая вниз степень двойки для x (>=1).
 | 
			
		||||
 * Нужна, чтобы построить маски вида (2^k - 1) и (2^(k+1) - 1).
 | 
			
		||||
 * При таких масках условие (hash & mask) == 0 имеет ожидаемый период ~2^k.
 | 
			
		||||
 */
 | 
			
		||||
@safe nothrow @nogc
 | 
			
		||||
private size_t floorPow2(size_t x)
 | 
			
		||||
{
 | 
			
		||||
	size_t p = 1;
 | 
			
		||||
	while ((p << 1) != 0 && (p << 1) <= x)
 | 
			
		||||
		p <<= 1;
 | 
			
		||||
	return p;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Batch-API: режет весь буфер и возвращает список чанков.
 | 
			
		||||
 * Удобно, если объём данных умеренный и важна простота.
 | 
			
		||||
 *
 | 
			
		||||
 * Сложность: O(n), где n — длина data.
 | 
			
		||||
 */
 | 
			
		||||
@safe
 | 
			
		||||
ChunkRange[] chunkify(const(ubyte)[] data, FastCDCParams params = FastCDCParams.init)
 | 
			
		||||
{
 | 
			
		||||
	FastCDCParams p = params;
 | 
			
		||||
	p.normalize(); // приводим пороги в адекватные границы
 | 
			
		||||
 | 
			
		||||
	ChunkRange[] chunks; // итоговый список диапазонов
 | 
			
		||||
	// Необязательный pre-reserve: уменьшает реаллокации; можно удалить, если не нужно.
 | 
			
		||||
	chunks.reserve(data.length / (p.avgSize ? p.avgSize : 1) + 1);
 | 
			
		||||
 | 
			
		||||
	// Строим две маски:
 | 
			
		||||
	//  - maskN (normal):  2^k - 1 → «частота» границ ~ avgSize
 | 
			
		||||
	//  - maskE (early):  2^(k+1) - 1 → «строже», реже до avgSize (не режем слишком рано)
 | 
			
		||||
	const size_t avgPow2 = floorPow2(p.avgSize);
 | 
			
		||||
	immutable uint maskN = cast(uint)(avgPow2 - 1);
 | 
			
		||||
	immutable uint maskE = cast(uint)(((avgPow2 << 1) != 0) ? ((avgPow2 << 1) - 1) : maskN);
 | 
			
		||||
 | 
			
		||||
	size_t start = 0; // начало текущего потенциального чанка
 | 
			
		||||
	uint rolling = 0; // текущее значение rolling-hash
 | 
			
		||||
 | 
			
		||||
	foreach (i, b; data) // идём по байтам
 | 
			
		||||
	{
 | 
			
		||||
		rolling = gearUpdate(rolling, b); // обновили rolling-hash
 | 
			
		||||
		const size_t clen = (i + 1) - start; // текущая длина кандидат-чанка
 | 
			
		||||
 | 
			
		||||
		// 1) До minSize границы запрещены — продолжаем копить байты.
 | 
			
		||||
		if (clen < p.minSize)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		// 2) Если превысили maxSize — принудительно режем тут.
 | 
			
		||||
		if (clen >= p.maxSize)
 | 
			
		||||
		{
 | 
			
		||||
			chunks ~= ChunkRange(start, clen);
 | 
			
		||||
			start = i + 1; // новый чанк начинается со следующего байта
 | 
			
		||||
			rolling = 0; // сбрасываем хэш (независимая последовательность)
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 3) Зона «ранняя» (min..avg): применяем более строгую маску, чтобы
 | 
			
		||||
		//    не нарезать слишком короткие чанки.
 | 
			
		||||
		if (clen < p.avgSize)
 | 
			
		||||
		{
 | 
			
		||||
			if ((rolling & maskE) == 0) // редкое совпадение → ставим границу
 | 
			
		||||
			{
 | 
			
		||||
				chunks ~= ChunkRange(start, clen);
 | 
			
		||||
				start = i + 1;
 | 
			
		||||
				rolling = 0;
 | 
			
		||||
			}
 | 
			
		||||
			continue; // иначе продолжаем наращивать чанк
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 4) Зона «нормальная» (avg..max): используем обычную маску — средний размер ≈ avgSize.
 | 
			
		||||
		if ((rolling & maskN) == 0)
 | 
			
		||||
		{
 | 
			
		||||
			chunks ~= ChunkRange(start, clen);
 | 
			
		||||
			start = i + 1;
 | 
			
		||||
			rolling = 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 5) Хвост: если что-то осталось — это последний чанк.
 | 
			
		||||
	if (start < data.length)
 | 
			
		||||
		chunks ~= ChunkRange(start, data.length - start);
 | 
			
		||||
 | 
			
		||||
	return chunks;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Стримовый API: вызывает sink(start,len) для каждого найденного чанка.
 | 
			
		||||
 *
 | 
			
		||||
 * Плюсы:
 | 
			
		||||
 *  - не аллоцирует список чанков (можно сделать @nogc-путь);
 | 
			
		||||
 *  - удобно сразу считать хэш чанка и писать в БД.
 | 
			
		||||
 *
 | 
			
		||||
 * Контракт sink:
 | 
			
		||||
 *  - возвращает 0 → «продолжать»;
 | 
			
		||||
 *  - возвращает != 0 → «остановить» (функция вернёт количество уже эмитнутых чанков).
 | 
			
		||||
 */
 | 
			
		||||
@safe
 | 
			
		||||
size_t processStream(const(ubyte)[] data, FastCDCParams params,
 | 
			
		||||
	scope int delegate(size_t, size_t) @safe sink)
 | 
			
		||||
{
 | 
			
		||||
	FastCDCParams p = params;
 | 
			
		||||
	p.normalize();
 | 
			
		||||
 | 
			
		||||
	const size_t avgPow2 = floorPow2(p.avgSize);
 | 
			
		||||
	immutable uint maskN = cast(uint)(avgPow2 - 1);
 | 
			
		||||
	immutable uint maskE = cast(uint)(((avgPow2 << 1) != 0) ? ((avgPow2 << 1) - 1) : maskN);
 | 
			
		||||
 | 
			
		||||
	size_t start = 0;
 | 
			
		||||
	uint rolling = 0;
 | 
			
		||||
	size_t count = 0; // числим эмитнутые чанки
 | 
			
		||||
 | 
			
		||||
	foreach (i, b; data)
 | 
			
		||||
	{
 | 
			
		||||
		rolling = gearUpdate(rolling, b);
 | 
			
		||||
		const size_t clen = (i + 1) - start;
 | 
			
		||||
 | 
			
		||||
		// До minSize границы не допускаются
 | 
			
		||||
		if (clen < p.minSize)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		// Решение «резать/не резать» для текущей позиции
 | 
			
		||||
		bool cut = false;
 | 
			
		||||
		if (clen >= p.maxSize) // принудительный разрыв на maxSize
 | 
			
		||||
			cut = true;
 | 
			
		||||
		else if (clen < p.avgSize) // раннее окно — строгая маска
 | 
			
		||||
			cut = ((rolling & maskE) == 0);
 | 
			
		||||
		else // нормальное окно — обычная маска
 | 
			
		||||
			cut = ((rolling & maskN) == 0);
 | 
			
		||||
 | 
			
		||||
		if (cut)
 | 
			
		||||
		{
 | 
			
		||||
			// Отдаём чанк потребителю. Он может попросить остановиться (!=0).
 | 
			
		||||
			if (sink(start, clen) != 0)
 | 
			
		||||
			{
 | 
			
		||||
				++count; // этот чанк уже отдан — учитываем
 | 
			
		||||
				return count; // и выходим
 | 
			
		||||
			}
 | 
			
		||||
			++count;
 | 
			
		||||
			start = i + 1; // следующий чанк начинается со следующего байта
 | 
			
		||||
			rolling = 0; // сбрасываем rolling-hash
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Хвостовой чанк (если есть): отдаём целиком.
 | 
			
		||||
	if (start < data.length)
 | 
			
		||||
	{
 | 
			
		||||
		if (sink(start, data.length - start) != 0)
 | 
			
		||||
		{
 | 
			
		||||
			++count; // учли последний чанк
 | 
			
		||||
			return count; // останов по сигналу sink
 | 
			
		||||
		}
 | 
			
		||||
		++count;
 | 
			
		||||
	}
 | 
			
		||||
	return count;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								source/gear.d
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								source/gear.d
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,66 @@
 | 
			
		|||
immutable ulong[256] gear = [
 | 
			
		||||
	0x2722039f43c57a70, 0x338c1bd5b7ac5204, 0xf9f2c73ff33c98c0, 0x7dee12e6cd31cb32,
 | 
			
		||||
	0x9688335e0f2decfd, 0x5307003c8e60b963, 0xfd2a2848eb358095, 0xc3614773074ee6b7,
 | 
			
		||||
	0x6e35235234b6ed0a, 0x9d4cfa9d8e3850cc, 0xaa1b3d8af8ad86bd, 0x79c6d2e28bfb333d,
 | 
			
		||||
	0x3df08966a00c33ec, 0xfd58bbf83f38c690, 0x5ef9ee9a4552545b, 0x099192a7e5599bdc,
 | 
			
		||||
	0xa8f2419947f21017, 0xd6a03d010f2fda7c, 0x1fe53de04074fc20, 0x75b5aff7c66605f8,
 | 
			
		||||
	0x1a94b7484bf509a9, 0xbf2e371a53466ded, 0xedcf13f8eb0f0fdf, 0xfba81285ead7dafe,
 | 
			
		||||
	0x839fb29274557fa5, 0xeefe64b15cc7f7f0, 0x7d15f8e862726515, 0x59b416e43cca2adc,
 | 
			
		||||
	0x9c2c925dcde12d4a, 0xf3df9373c3e63a07, 0x747cb5ec08ffa4ef, 0x26c93138f3f19b29,
 | 
			
		||||
	0xcdade11723bd59ed, 0xc7a6a7d0c18642cb, 0x88c2976f22f5d084, 0x7c48f54cdaf480fe,
 | 
			
		||||
	0x91ea7c7fd3d06d54, 0xed3e31236e8c9366, 0xa16da2234f420bc4, 0x5ee6136449d30974,
 | 
			
		||||
	0xe32a921921181e16, 0xa6ab2fb8c7212257, 0x754a8a581ce819ca, 0x22b2de3e7c7a2f57,
 | 
			
		||||
	0xd2773285e49b9160, 0x19b0449384554129, 0x145e7d2c46da460e, 0xdd720d0e79a3615d,
 | 
			
		||||
	0x621ae4f0ad576223, 0x4f6da235cc25d5c9, 0x6d6d39e005d67437, 0x5839c8f3d2f71122,
 | 
			
		||||
	0x691a668bc7f5c153, 0xb2eb484f8546c61d, 0x7955c346c34cbcdc, 0x413c2a0ba6fd0a88,
 | 
			
		||||
	0x29ad3d418323592b, 0xb7d04d0abf3f8d50, 0x76e742f91dfc77ea, 0x8a5c80d1a0d5ff5c,
 | 
			
		||||
	0xce1aa9b0bdd16adc, 0x74e4bd6f412c8186, 0xbf1dddc8f63dfc08, 0x11dcb84c1b5c32cb,
 | 
			
		||||
	0x3320ed259fc0d8c0, 0x13dbd4c934c58e01, 0x344b61dd3741a9f9, 0x935861bea84a6f81,
 | 
			
		||||
	0xaf70eea3844052f9, 0xc0a83f93799c2e81, 0xdd23b2a943a5af16, 0x05b4efd89b3e818b,
 | 
			
		||||
	0x75b2a3d0fe099aec, 0x5aab5599ae580c37, 0xe9b64238ed626a6b, 0xb63f47c31c31ec1d,
 | 
			
		||||
	0x0b50ee03c3425dde, 0xf287ebed924249f6, 0xe09eee62604318c4, 0x0d334cb1bd82bc13,
 | 
			
		||||
	0xc41abf3d95b18620, 0x869c3bc45e2c9edf, 0x526de53484e093c7, 0xc640fee4784fd9ce,
 | 
			
		||||
	0x761637787d81c0ea, 0x817bf175cb17e903, 0x94a4846f1158f988, 0x99c254e5f8e698e0,
 | 
			
		||||
	0xa4623d4d1b76352e, 0x326dae4493475c3a, 0xed2944be79511208, 0x163a0a9b65f40339,
 | 
			
		||||
	0x336489f8c2f6190c, 0x670d217f8e6bee33, 0x662e19c285c5a4a1, 0xcab8f4512d0b251a,
 | 
			
		||||
	0x61d4476812ed1017, 0x0ec77209307430af, 0x20a94905901093dc, 0xaa9fe2cae9ffa699,
 | 
			
		||||
	0xc75f757de6c045dc, 0x141ef38478656459, 0x9b3ce9c4e3dd7858, 0x3ab62b9aa45a3d0d,
 | 
			
		||||
	0x61a89423e18e5e68, 0x3802972ecadf592d, 0xcfc85c5724ff3af8, 0x381ee916e97a628a,
 | 
			
		||||
	0x2fa2c37a040e488a, 0x9813a505b4ca4036, 0xc4254f1aaf7b2f42, 0xe8a0720b79a1188d,
 | 
			
		||||
	0xe663a71adb5d53e3, 0x6e3b5927934102af, 0xbd8c502741b1fcb1, 0x1af6fa2fb1d2e5a6,
 | 
			
		||||
	0xc88d367a79d06f5d, 0x29fe7cdab66530d9, 0x34bef2ebe612d95f, 0x9ab6977a57db1fa2,
 | 
			
		||||
	0x73774fc29deac09a, 0x7832f37495fd28fb, 0x1559a3badfbd42a6, 0x7e6831522a50d2bc,
 | 
			
		||||
	0xddb8564f3aafe3b7, 0x86acb9eca71bc09d, 0x21b0a9469727d4fc, 0x26d3b66f525ebcab,
 | 
			
		||||
	0x77e3fd126fd97e3a, 0x5306b81a9fe2a92e, 0x7292138f116d8911, 0x285b466c9939b076,
 | 
			
		||||
	0x40527805d9a4379d, 0x8986c05119c7ca1e, 0x6a7890c402303c31, 0xb1b109dc109405bc,
 | 
			
		||||
	0x1d71f3997b288f30, 0xfa203ff4dc9ea72c, 0x8ae3eea975cc92da, 0x3468e4305eabb928,
 | 
			
		||||
	0xd79c37e720467df1, 0x011e6490c0f832d2, 0x29ce2ada8509647a, 0xb4e325b9f3ba783c,
 | 
			
		||||
	0xa812ca4fad720763, 0x0cdf098645ccb476, 0xf6b47e21637fcd76, 0x3597f48148a297de,
 | 
			
		||||
	0x5875212868ab81ec, 0x1ea36243940596bb, 0xfd93ac7c39a27586, 0xabb09b0f803a7214,
 | 
			
		||||
	0x8cc8ec1ea21a16af, 0x824a0db50ae906d1, 0x3d972fb701ca3e70, 0xda60d493e9a20bd0,
 | 
			
		||||
	0x97d282f6bda26087, 0x9bc8f7842af296d0, 0x14804a1663a0cf7e, 0x3b71cc25885e75f3,
 | 
			
		||||
	0x131adc05e336042b, 0x566aa36d26eee86c, 0x97d4c4d4fd4b0dd1, 0xd2407b1485c7bee1,
 | 
			
		||||
	0xcad613e7b92e6df1, 0xe3ceccd99d975088, 0x99e6b93ff96a2636, 0x1ad75dbed057f0d0,
 | 
			
		||||
	0x5e3ba609dd100c6e, 0x9c5efa00b33a18f3, 0xad89369e692bdb28, 0xf7a546fca26d1d7d,
 | 
			
		||||
	0x5813db1fe943575f, 0x24c3467f03a144ae, 0xc892f2ce492cb7c8, 0xc44672263508d34b,
 | 
			
		||||
	0xd400e1c0a5734a40, 0x3ca24ee74bf8e84f, 0xd83bd4e907c351a5, 0xe142297005fa9aa8,
 | 
			
		||||
	0x0f6d796cf68abda0, 0x6c8e25bc6d9ae2e8, 0xccc235f322a42cf3, 0xabaf39cea8ca450c,
 | 
			
		||||
	0x02b9cdf615a0d7b6, 0x8aaf7d8b55d4dc39, 0xbe2c2bc6ef13c6c5, 0x6ad98aa4a4bc610f,
 | 
			
		||||
	0x1051a62ac2a2b434, 0xbd167e6eba260d35, 0xb9b86ac04ac4f811, 0xabe8a6453e196739,
 | 
			
		||||
	0x439ff734b19246b4, 0xcea324040c9e8981, 0x87f55cf1035e1a22, 0xa227d679c33597f9,
 | 
			
		||||
	0xbf4d654b6cdd0015, 0xc0302ec55f87a46e, 0xed32173466c70a83, 0x8ceb757b648d2bf2,
 | 
			
		||||
	0x1873757a6d17446b, 0xeb0f366fea62e77e, 0x145aa2795d34dd93, 0x2fc378be4c471db0,
 | 
			
		||||
	0x6d1274fb8f6364a2, 0x602a56fd1cc36728, 0x5f8aa6e0c892b4b5, 0x33e2c5653d8b1ad6,
 | 
			
		||||
	0x1f6c8b2a004714f4, 0x4042b98d54acbfef, 0x4606386f11f6456f, 0xf56bd21a8a35c540,
 | 
			
		||||
	0xd2b23c57b3718e1f, 0x94726832fe96e61d, 0xa225b072752a823b, 0x0bd957cf585f8cda,
 | 
			
		||||
	0x533d819bb30b4221, 0xda0f9cff9a0115fa, 0xd14de3b8fe3354ea, 0xa96328e96d9364c0,
 | 
			
		||||
	0x9078dc0eff2676ab, 0x22585cd4521c6210, 0x5903254df4e402a5, 0x1b54b71b55ae697a,
 | 
			
		||||
	0xb899b86756b2aa39, 0x5d5d2dd5cd0bce8b, 0x7b3a78a4a0662015, 0xa9fbfc7678fc7931,
 | 
			
		||||
	0xa732d694f6ab64a0, 0x9fc960e7db3e9716, 0x76c765948f3c2ba5, 0x076a509dca2a4349,
 | 
			
		||||
	0xca5bfc5973661e59, 0x454ec4d49bddd45d, 0x56115e001997cee2, 0xd689eb8926051c7f,
 | 
			
		||||
	0xf50df8ca9c355e3f, 0x88a375a9f0492a69, 0xe059fd001d50439a, 0x765c5d6f66d5e788,
 | 
			
		||||
	0xaf57f4eea178f896, 0x06e8cca68730fbbd, 0xb7b1f6f86904ce4e, 0x3c3b10b0c08cf0bf,
 | 
			
		||||
	0x1e0e310524778bd4, 0xd65d7cd93cde7c69, 0x18543b187c77fcf3, 0x180f6cdd1af3a60a,
 | 
			
		||||
	0xe1cd4c2bc3656704, 0x218fdfc5aa282d00, 0x844eeaf2e439b242, 0x05df1a59e415b4c6,
 | 
			
		||||
	0x14abdd3ace097c2c, 0x7f3b0705b04b14d4, 0xf69c57f60180332b, 0x165fc3f0e65db80f
 | 
			
		||||
];
 | 
			
		||||
							
								
								
									
										72
									
								
								source/text.d
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								source/text.d
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,72 @@
 | 
			
		|||
string text = "Цель науки о данных — улучшить процесс принятия решений, " ~
 | 
			
		||||
	"основывая их на более глубоком понимании ситуации с помощью " ~
 | 
			
		||||
	"анализа больших наборов данных. Как область деятельности " ~
 | 
			
		||||
	"наука о данных включает в себя ряд принципов, методов " ~
 | 
			
		||||
	"постановки задач, алгоритмов и процессов для выявления " ~
 | 
			
		||||
	"скрытых полезных закономерностей в больших наборах данных. " ~
 | 
			
		||||
	"Она тесно связана с глубинным анализом данных и машинным " ~
 | 
			
		||||
	"обучением, но имеет более широкий охват. Сегодня наука о " ~
 | 
			
		||||
	"данных управляет принятием решений практически во всех " ~
 | 
			
		||||
	"сферах современного общества. В повседневной жизни вы " ~
 | 
			
		||||
	"ощущаете на себе воздействие науки о данных, когда видите " ~
 | 
			
		||||
	"отобранные специально для вас рекламные объявления, " ~
 | 
			
		||||
	"рекомендованные фильмы и книги, ссылки на предполагаемых " ~
 | 
			
		||||
	"друзей, отфильтрованные письма в папке со спамом, " ~
 | 
			
		||||
	"персональные предложения от мобильных операторов и " ~
 | 
			
		||||
	"страховых компаний. Она влияет на порядок переключения и " ~
 | 
			
		||||
	"длительность сигналов светофоров в вашем районе, на то, как " ~
 | 
			
		||||
	"были созданы новые лекарства, продающиеся в аптеке, и то, как " ~
 | 
			
		||||
	"полиция вычисляет, где может потребоваться ее присутствие. " ~
 | 
			
		||||
	"Рост использования науки о данных в обществе обусловлен " ~
 | 
			
		||||
	"появлением больших данных и социальных сетей, увеличением " ~
 | 
			
		||||
	"вычислительной мощности, уменьшением размеров носителей " ~
 | 
			
		||||
	"компьютерной памяти и разработкой более эффективных " ~
 | 
			
		||||
	"методов анализа и моделирования данных, таких как глубокое " ~
 | 
			
		||||
	"обучение. Вместе эти факторы означают, что сейчас процесс " ~
 | 
			
		||||
	"сбора, хранения и обработки данных стал как никогда ранее " ~
 | 
			
		||||
	"доступен для организаций. В то же время эти технические " ~
 | 
			
		||||
	"новшества и растущее применение науки о данных означают, что " ~
 | 
			
		||||
	"этические проблемы, связанные с использованием данных и " ~
 | 
			
		||||
	"личной конфиденциальностью, тоже вышли на первый план. Цель " ~
 | 
			
		||||
	"этой книги — познакомить с наукой о данных на уровне ее " ~
 | 
			
		||||
	"основных элементов и с той степенью погружения, которая " ~
 | 
			
		||||
	"обеспечит принципиальное понимание вопроса. " ~
 | 
			
		||||
	"Глава 1 очерчивает область науки о данных и дает краткую " ~
 | 
			
		||||
	"историю ее становления и эволюции. В ней мы также " ~
 | 
			
		||||
	"рассмотрим, почему наука о данных стала такой востребованной " ~
 | 
			
		||||
	"сегодня, и перечислим факторы, стимулирующие ее внедрение. В " ~
 | 
			
		||||
	"конце главы мы развенчаем несколько мифов, связанных с темой " ~
 | 
			
		||||
	"книги. Глава 2 вводит фундаментальные понятия, относящиеся к " ~
 | 
			
		||||
	"данным. В ней также описаны стандартные этапы проекта: " ~
 | 
			
		||||
	"понимание бизнес-целей, начальное изучение данных, " ~
 | 
			
		||||
	"подготовка данных, моделирование, оценка и внедрение. Глава 3 " ~
 | 
			
		||||
	"посвящена инфраструктуре данных и проблемам, связанным с " ~
 | 
			
		||||
	"большими данными и их интеграцией из нескольких источников. " ~
 | 
			
		||||
	"Одна из таких типичных проблем заключается в том, что данные " ~
 | 
			
		||||
	"в базах и хранилищах находятся на одних серверах, а " ~
 | 
			
		||||
	"анализируются на других. Поэтому колоссальное время тратится " ~
 | 
			
		||||
	"на перемещение больших наборов данных между этими " ~
 | 
			
		||||
	"серверами. Глава 3 начинается с описания типичной " ~
 | 
			
		||||
	"инфраструктуры науки о данных для организации и некоторых " ~
 | 
			
		||||
	"свежих решений проблемы перемещения больших наборов " ~
 | 
			
		||||
	"данных, а именно: метода машинного обучения в базе данных, " ~
 | 
			
		||||
	"использования Hadoop для хранения и обработки данных, а также " ~
 | 
			
		||||
	"разработки гибридных систем, в которых органично сочетаются " ~
 | 
			
		||||
	"традиционное программное обеспечение баз данных и решения, " ~
 | 
			
		||||
	"подобные Hadoop. Глава завершается описанием проблем, " ~
 | 
			
		||||
	"связанных с интеграцией данных в единое представление для " ~
 | 
			
		||||
	"последующего машинного обучения. Глава 4 знакомит читателя с " ~
 | 
			
		||||
	"машинным обучением и объясняет некоторые из наиболее " ~
 | 
			
		||||
	"популярных алгоритмов и моделей, включая нейронные сети, " ~
 | 
			
		||||
	"глубокое обучение и деревья решений. В главе 5 основное " ~
 | 
			
		||||
	"внимание уделяется использованию опыта в области машинного " ~
 | 
			
		||||
	"обучения для решения реальных задач, приводятся примеры " ~
 | 
			
		||||
	"анализа стандартных бизнес-проблем и того, как они могут быть " ~
 | 
			
		||||
	"решены с помощью машинного обучения. В главе 6 " ~
 | 
			
		||||
	"рассматриваются этические вопросы науки о данных, последние " ~
 | 
			
		||||
	"разработки в области регулирования и некоторые из новых " ~
 | 
			
		||||
	"вычислительных методов защиты конфиденциальности в " ~
 | 
			
		||||
	"процессе обработки данных. Наконец, в главе 7 описаны сферы, " ~
 | 
			
		||||
	"на которые наука о данных окажет наибольшее влияние в " ~
 | 
			
		||||
	"ближайшем будущем, изложены принципы, позволяющие " ~
 | 
			
		||||
	"определить, будет ли данный конкретный проект успешным.";
 | 
			
		||||
							
								
								
									
										48
									
								
								tools/gen.d
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										48
									
								
								tools/gen.d
									
										
									
									
									
										Executable file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,48 @@
 | 
			
		|||
#!/usr/bin/rdmd
 | 
			
		||||
module tools.gen;
 | 
			
		||||
 | 
			
		||||
import std.stdio;
 | 
			
		||||
import std.random : Random, unpredictableSeed, uniform;
 | 
			
		||||
 | 
			
		||||
void main()
 | 
			
		||||
{
 | 
			
		||||
	enum N = 256;
 | 
			
		||||
	ulong[N] gear;
 | 
			
		||||
 | 
			
		||||
	auto rng = Random(unpredictableSeed);
 | 
			
		||||
 | 
			
		||||
	bool[ulong] seen;
 | 
			
		||||
	ulong[] vals;
 | 
			
		||||
	vals.reserve(N);
 | 
			
		||||
 | 
			
		||||
	while (vals.length < N)
 | 
			
		||||
	{
 | 
			
		||||
		const v = uniform!ulong(rng);
 | 
			
		||||
		if (v in seen)
 | 
			
		||||
			continue;
 | 
			
		||||
		seen[v] = true;
 | 
			
		||||
		vals ~= v;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gear[] = vals[0 .. N];
 | 
			
		||||
 | 
			
		||||
	writeln("immutable ulong[256] gear = [");
 | 
			
		||||
	foreach (i, v; gear)
 | 
			
		||||
	{
 | 
			
		||||
		// Индент только в начале каждой строки
 | 
			
		||||
		if (i % 4 == 0)
 | 
			
		||||
			write("\t");
 | 
			
		||||
 | 
			
		||||
		writef("0x%016x", v);
 | 
			
		||||
 | 
			
		||||
		if (i != N - 1) // не ставим запятую после последнего
 | 
			
		||||
			write(",");
 | 
			
		||||
 | 
			
		||||
		// Перенос после каждого 4-го элемента или в самом конце
 | 
			
		||||
		if ((i + 1) % 4 == 0 || i == N - 1)
 | 
			
		||||
			writeln();
 | 
			
		||||
		else
 | 
			
		||||
			write(" ");
 | 
			
		||||
	}
 | 
			
		||||
	writeln("];");
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue