diff --git a/README.md b/README.md index 501c486..1a9e61f 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,133 @@ # Implementing Change Data Capture -## Объяснение изменений и особенностей +## Объяснение алгоритма FastCDC -1. **Статическая таблица Gear**: - - Таблица `gear` определена как `immutable ulong[256]` с 256 случайными 64-битными числами, сгенерированными заранее (вместо динамической генерации с `Random`). Значения выбраны произвольно, но в стиле реализаций FastCDC, где таблица фиксирована для консистентности. - - Использование `immutable` обеспечивает неизменяемость и оптимизацию компилятором D. - - В реальных системах таблица может быть взята из оригинальной статьи FastCDC или сгенерирована с криптографически безопасным ГСЧ, но здесь она статична для простоты и воспроизводимости. +FastCDC представляет собой алгоритм для разбиения данных на "куски" (чанки) на основе их содержания, что позволяет экономить пространство в хранилищах. Этот метод быстрее и эффективнее традиционных подходов, таких как Rabin или Gear. -2. **Текст**: - - Переносы строк (`\n`) заменены на пробелы, как указано в запросе. Длина текста в UTF-8 составляет 7471 байт (кириллица — 2 байта на символ, пробелы и знаки — 1 байт). - - Текст конвертируется в `ubyte[]` для обработки FastCDC, который работает на уровне байтов. +### Шаг 1: Что Такое Content-Defined Chunking (CDC) и Зачем Это Нужно? +Рассмотрим большой файл, например, видео или архив. При изменении нескольких байт весь файл может восприниматься как новый, что приводит к записи дубликатов и потере места. CDC решает эту проблему: данные разбиваются на переменные куски (чанки) не по фиксированной длине (например, 8 КБ), а по содержанию. При небольших изменениях большинство чанков остаются неизменными и не требуют перезаписи. -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). +- **Проблема с фиксированным разбиением (Fixed-Size Chunking)**: Вставка байта в начало сдвигает все чанки, и система считает весь файл новым. +- **Как работает базовый CDC**: Производится "скольжение" окна по байтам файла, вычисляется хэш (отпечаток), и определяются "границы" чанков, где хэш соответствует условию (например, заканчивается нулями). -4. **Функция `fastcdc`**: - - Сигнатура упрощена: удалён параметр `gear`, так как таблица теперь глобальная и статическая (`immutable gear`). - - Логика осталась без изменений: инициализация хэша `fp` для первых `minSize` байт без проверок, фаза `maskS` до `normalSize`, затем `maskL` до `maxSize` или cut-point (`fp & mask == 0`). +FastCDC является оптимизированной версией Gear-based CDC. Gear применяет быстрый хэш, но работает медленнее из-за частых проверок и неравномерных чанков. FastCDC ускоряет процесс в 3–5 раз, сохраняя высокую эффективность. -5. **Вывод**: - - Код выводит общую длину текста, номер чанка, его размер и первые 50 символов содержимого (или всё, если короче). - - Пример вывода (зависит от Gear-таблицы): - ``` - Total length (bytes): 7471 - Chunk sizes and contents: - Chunk 1: 239 bytes - Content: Цель науки о данных — улучшить процесс принятия решений, о... - - Chunk 2: 204 bytes - Content: сновывая их на более глубоком понимании ситуации с помощью ... - - Chunk 3: 211 bytes - Content: анализа больших наборов данных. Как область деятельности на... - ... - Chunk 31: 65 bytes - Content: определить, будет ли данный конкретный проект успешным. - ``` +### Шаг 2: Три Ключевые "Фишки" FastCDC +FastCDC использует три основных улучшения для достижения скорости и эффективности: -6. **Особенности работы**: - - **Rolling hash**: Для каждого байта `fp = (fp << 1) + gear[byte]`. Статическая таблица `gear` обеспечивает одинаковые результаты при каждом запуске. - - **Нормализация**: `maskS` делает cut-points реже до 200 байт, `maskL` — чаще после, поддерживая чанки около 200 байт. - - **UTF-8**: Границы чанков байтовые, могут не совпадать с границами слов из-за кириллицы (2 байта на символ). - - **Дедупликация**: Для проверки дубликатов можно добавить хэширование чанков (например, SHA-256), но это не включено в текущий код. +1. **Оптимизация Проверки Хэша (Simplified and Enhanced Hash Judgment)**: + - В традиционных методах проверка хэша сложна (например, через деление modulo). FastCDC упрощает процесс с помощью побитового AND (&) с маской (фильтром из бит). + - Аналогия: Вместо проверки делимости числа на 100 используется проверка на окончание двумя нулями — это быстрее для вычислений. + - Дополнительно: Расширяется "окно" хэша до 48 бит с нулями в маске, что обеспечивает равномерность чанков и снижает коллизии позиций. + +2. **Пропуск Границ для Маленьких Чанков (Sub-Minimum Chunk Cut-Point Skipping)**: + - Если потенциальный чанк меньше минимального порога (например, 2 КБ), возможная граница игнорируется, и вычисление хэша продолжается. + - Аналогия: При чтении книги конец абзаца пропускается, если абзац короче двух страниц, чтобы избежать траты времени на мелкие фрагменты. + - Это ускоряет алгоритм, хотя может слегка снизить эффективность дедупликации (на 15%), делая некоторые чанки крупнее. + +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 с дедупликацией). diff --git a/dub.json b/dub.json index 7ebfb58..fabf1f1 100644 --- a/dub.json +++ b/dub.json @@ -7,6 +7,5 @@ "license": "BSL-1.0", "name": "cdc", "targetPath": "bin", - "targetType": "executable", - "dflags": ["-J=source"] + "targetType": "executable" } \ No newline at end of file diff --git a/source/app.d b/source/app.d index 5b5bd12..daf6cd5 100644 --- a/source/app.d +++ b/source/app.d @@ -1,80 +1,409 @@ -import std.stdio; +module app; -// Статическая таблица Gear (256 случайных 64-битных чисел) -mixin(import("gear.d")); +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; -ulong fastcdc(const ubyte[] src, size_t n, size_t minSize, size_t maxSize, size_t normalSize, ulong maskS, ulong maskL) +import fastcdc; // твой модуль FastCDC + +// ---------- утилиты ---------- + +// У Phobos read(...) на некоторых версиях возвращает void[]. +// Безопасно приводим к ubyte[] в @trusted-обёртке. +@trusted ubyte[] readBytes(string path) { - if (n <= minSize) - return n; - if (n > maxSize) - n = maxSize; - if (n < normalSize) - normalSize = n; + auto v = read(path); // void[] + return cast(ubyte[]) v; // это новый буфер байт → безопасно +} - ulong fp = 0; +// 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/.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/..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/.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/.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" size_t i = 0; + while (i < lines.length && !lines[i].strip.startsWith("ord")) + ++i; + enforce(i < lines.length, "Не найден заголовок секции данных в манифесте"); + ++i; - // Инициализация fp для первых minSize байт (без проверки cut-point) - while (i < minSize) - { - 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++; + auto dst = File(opt.outFile, "wb"); + size_t count = 0; + for (; i < lines.length; ++i) + { + 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; } - return n; // Достигнут max + + dst.close(); + writefln("restore: %s <- %s (chunks: %u)", opt.outFile, realManifest, cast(uint) count); + return 0; } -void main() +// ---------- CLI ---------- + +@safe +void printHelp(string prog) { - // Текст - mixin(import("text.d")); + writeln("Usage:"); + writeln(" ", + prog, " split --store [--profile text|bin] [--min N] [--avg N] [--max N] [--log-every N]"); + writeln(" ", prog, " restore --store "); +} - // Конвертация в 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) +int main(string[] args) +{ // без @safe: getopt берёт &var + if (args.length < 2) { - 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++; + 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; } } diff --git a/source/fastcdc.d b/source/fastcdc.d new file mode 100644 index 0000000..0178eb9 --- /dev/null +++ b/source/fastcdc.d @@ -0,0 +1,272 @@ +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; +} diff --git a/source/gear.d b/source/gear.d deleted file mode 100644 index d5cd153..0000000 --- a/source/gear.d +++ /dev/null @@ -1,66 +0,0 @@ -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 -]; diff --git a/source/text.d b/source/text.d deleted file mode 100644 index fb4d600..0000000 --- a/source/text.d +++ /dev/null @@ -1,72 +0,0 @@ -string text = "Цель науки о данных — улучшить процесс принятия решений, " ~ - "основывая их на более глубоком понимании ситуации с помощью " ~ - "анализа больших наборов данных. Как область деятельности " ~ - "наука о данных включает в себя ряд принципов, методов " ~ - "постановки задач, алгоритмов и процессов для выявления " ~ - "скрытых полезных закономерностей в больших наборах данных. " ~ - "Она тесно связана с глубинным анализом данных и машинным " ~ - "обучением, но имеет более широкий охват. Сегодня наука о " ~ - "данных управляет принятием решений практически во всех " ~ - "сферах современного общества. В повседневной жизни вы " ~ - "ощущаете на себе воздействие науки о данных, когда видите " ~ - "отобранные специально для вас рекламные объявления, " ~ - "рекомендованные фильмы и книги, ссылки на предполагаемых " ~ - "друзей, отфильтрованные письма в папке со спамом, " ~ - "персональные предложения от мобильных операторов и " ~ - "страховых компаний. Она влияет на порядок переключения и " ~ - "длительность сигналов светофоров в вашем районе, на то, как " ~ - "были созданы новые лекарства, продающиеся в аптеке, и то, как " ~ - "полиция вычисляет, где может потребоваться ее присутствие. " ~ - "Рост использования науки о данных в обществе обусловлен " ~ - "появлением больших данных и социальных сетей, увеличением " ~ - "вычислительной мощности, уменьшением размеров носителей " ~ - "компьютерной памяти и разработкой более эффективных " ~ - "методов анализа и моделирования данных, таких как глубокое " ~ - "обучение. Вместе эти факторы означают, что сейчас процесс " ~ - "сбора, хранения и обработки данных стал как никогда ранее " ~ - "доступен для организаций. В то же время эти технические " ~ - "новшества и растущее применение науки о данных означают, что " ~ - "этические проблемы, связанные с использованием данных и " ~ - "личной конфиденциальностью, тоже вышли на первый план. Цель " ~ - "этой книги — познакомить с наукой о данных на уровне ее " ~ - "основных элементов и с той степенью погружения, которая " ~ - "обеспечит принципиальное понимание вопроса. " ~ - "Глава 1 очерчивает область науки о данных и дает краткую " ~ - "историю ее становления и эволюции. В ней мы также " ~ - "рассмотрим, почему наука о данных стала такой востребованной " ~ - "сегодня, и перечислим факторы, стимулирующие ее внедрение. В " ~ - "конце главы мы развенчаем несколько мифов, связанных с темой " ~ - "книги. Глава 2 вводит фундаментальные понятия, относящиеся к " ~ - "данным. В ней также описаны стандартные этапы проекта: " ~ - "понимание бизнес-целей, начальное изучение данных, " ~ - "подготовка данных, моделирование, оценка и внедрение. Глава 3 " ~ - "посвящена инфраструктуре данных и проблемам, связанным с " ~ - "большими данными и их интеграцией из нескольких источников. " ~ - "Одна из таких типичных проблем заключается в том, что данные " ~ - "в базах и хранилищах находятся на одних серверах, а " ~ - "анализируются на других. Поэтому колоссальное время тратится " ~ - "на перемещение больших наборов данных между этими " ~ - "серверами. Глава 3 начинается с описания типичной " ~ - "инфраструктуры науки о данных для организации и некоторых " ~ - "свежих решений проблемы перемещения больших наборов " ~ - "данных, а именно: метода машинного обучения в базе данных, " ~ - "использования Hadoop для хранения и обработки данных, а также " ~ - "разработки гибридных систем, в которых органично сочетаются " ~ - "традиционное программное обеспечение баз данных и решения, " ~ - "подобные Hadoop. Глава завершается описанием проблем, " ~ - "связанных с интеграцией данных в единое представление для " ~ - "последующего машинного обучения. Глава 4 знакомит читателя с " ~ - "машинным обучением и объясняет некоторые из наиболее " ~ - "популярных алгоритмов и моделей, включая нейронные сети, " ~ - "глубокое обучение и деревья решений. В главе 5 основное " ~ - "внимание уделяется использованию опыта в области машинного " ~ - "обучения для решения реальных задач, приводятся примеры " ~ - "анализа стандартных бизнес-проблем и того, как они могут быть " ~ - "решены с помощью машинного обучения. В главе 6 " ~ - "рассматриваются этические вопросы науки о данных, последние " ~ - "разработки в области регулирования и некоторые из новых " ~ - "вычислительных методов защиты конфиденциальности в " ~ - "процессе обработки данных. Наконец, в главе 7 описаны сферы, " ~ - "на которые наука о данных окажет наибольшее влияние в " ~ - "ближайшем будущем, изложены принципы, позволяющие " ~ - "определить, будет ли данный конкретный проект успешным."; diff --git a/tools/gen.d b/tools/gen.d deleted file mode 100755 index d05f26d..0000000 --- a/tools/gen.d +++ /dev/null @@ -1,48 +0,0 @@ -#!/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("];"); -}