Russian Qt Forum

Qt => Общие вопросы => Тема начата: Igors от Январь 26, 2020, 11:10



Название: Сохранить "диффы"
Отправлено: Igors от Январь 26, 2020, 11:10
Добрый день

Молодежный термин "диффы" мелькает довольно часто и подразумевается что, мол, тут все ясно :) Ну "в прынцыпе" да, ясно, а конкретно - вот как-то не очень.

Есть 2 блока памяти одинакового размера, предполагается что многие байты совпадают. Нужно сохранить "только изменения" так чтобы имея первый блок + изменения получить второй.

Не то чтобы я не могу написать (и хочу чтобы кто-то сделал за меня), но как-то у меня получается безумно длинно и сложно, может чего-то не вижу?

Спасибо


Название: Re: Сохранить "диффы"
Отправлено: Azazello от Январь 26, 2020, 13:44

Есть 2 блока памяти одинакового размера, предполагается что многие байты совпадают. Нужно сохранить "только изменения" так чтобы имея первый блок + изменения получить второй.

Не то чтобы я не могу написать (и хочу чтобы кто-то сделал за меня), но как-то у меня получается безумно длинно и сложно, может чего-то не вижу?

Спасибо

Да, бывает. Как зависнишь иногда над чем то.
Код:
#include <QCoreApplication>

#include <QLatin1String>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QByteArray s1("Test");
    QByteArray s2("Lkst");

    struct diff {
        int startPos;
        int size;
        char *data;
    };

    QVector <diff> diffs;

    int diffPos = -1;
    for (int i = 0; i < s1.size(); ++i) {
        if (s1[i] != s2[i]) {
            if (diffPos == -1)
                diffPos = i;
        } else {
            if (diffPos != -1 ) {
                int size = i - diffPos;
                diffs.append({diffPos,size,s2.data() + diffPos});
                diffPos = -1;
            }
        }
    }

    //diff  in last element(s)
    if (diffPos != -1) {
        int size = s1.size() - diffPos;
        diffs.append({diffPos,size,s2.data() + diffPos});
    }

    //output diffs
    for (auto &i: diffs) {
        qDebug() << QByteArray(i.data,i.size);
    }

   QByteArray restored(s1);
    //restore
    for (auto &i: diffs) {
        restored.replace(i.startPos,i.size,i.data,i.size);
    }

   //output restore mem block from the source by diffs
    qDebug() << restored;

    return a.exec();
}


На полноту тестирования не претендую, думаю смысл упаковки понятен - сохраняем позицию, размер и указатель на начало данных.

Учитывая, что работа с памятью и вам нужно это быстро делать - _mm_loadu_si128, _mm_cmpeq_epi8
Либо в исходники QByteArray (или интет), либо использовать QByteArray как источник данных (QByteArray::compare - там тоже на sse, правда только для поиска вхождения возможно использовать).

На самом деле в плане оптимизации производительности задача не такая уж тривиальная, как кажется.
На SSE, даже если будет больше кода, выигрышь в производительности будет ощутим (два порядка - т.е. в 4 раза запросто в человеской десятичной системе) - обработка пачками, что уменьшит количество итераций цикла for (предсказание ветвлений) и много других плюшек.
Если же задача оптимизации объема занимаемой памяти (например для серилизации памяти на диск), выигрышь будет ощутим даже на чистом с++ (что выше). Опять же, каков процент изменений, дифы в цепочках будут? Вопросов много. Это я так. Вы уж их сами себе позадавайте.



Название: Re: Сохранить "диффы"
Отправлено: Igors от Январь 27, 2020, 10:47
Код
C++ (Qt)
diffs.append({diffPos,size,s2.data() + diffPos});
Так Вы храните указатель на данные s2, а на восстановлении s2 отсутствует, его и надо получить.

Если же задача оптимизации объема занимаемой памяти (например для серилизации памяти на диск), выигрышь будет ощутим даже на чистом с++ (что выше). Опять же, каков процент изменений, дифы в цепочках будут?
Да, запись в файл. Вчера гонял один проект юзера. 140 объектов, 300 кадров. Пакует неплохо, напр из 500K делает 16K. Но выходной файл все же 660 Mb и пишется задумчиво.. В данном случае это не катастрофа, но и объектов и кадров запросто может быть в 10 раз больше. И что делать - хз. Какие-то zip(ы) - вряд ли, там в основном флоты, жмутся плохо.

Просто "к слову". Если существительное оканчивается на шипящую, то для женского рода мягкий знак пишется (мышь, чушь, печь), а для мужского нет (малыш, товарищ, выигрыш).


Название: Re: Сохранить "диффы"
Отправлено: Azazello от Январь 27, 2020, 11:58
Так Вы храните указатель на данные s2, а на восстановлении s2 отсутствует, его и надо получить.

Так измените структуру на:
Код:
struct diff {
        int startPos;
        QByteArray data;
    };

Этож всего лишь пример. Тем более если Вы сохраняете, предыдущие данные присутствует, поэтому будет лишнее копирование.


Название: Re: Сохранить "диффы"
Отправлено: Igors от Январь 27, 2020, 13:13
Так измените структуру на:
Код:
struct diff {
        int startPos;
        QByteArray data;
    };
1) Наверное startPos все-таки unsigned int. Академически даже size_t но на это наверно лучше забить, считаем блоков > 4Gb нет.

2) Копировать данные в QByteArray data - ну на выходе требуется блок памяти для сериализации (а не контейнеры). Вылить вектор diff в поток можно, но тогда лучше делать это с той структурой что Вы сначала предложили.

3) Пусть пакуются данные байты которых совпадают "через один", напр строки
Цитировать
abcdefgh
aacceegg
При этом упакованный размер намного превысит изначальный

Раздражает, да? Меня тоже :) Какая-то совершенно глупая задачка (явно не стоит выеденного яйца), а из нее все лезут и лезут проблемы, та шо такое  :'(


Название: Re: Сохранить "диффы"
Отправлено: Azazello от Январь 27, 2020, 13:24
1) Наверное startPos все-таки unsigned int. Академически даже size_t но на это наверно лучше забить, считаем блоков > 4Gb нет.

Демагогия началась. Qt - int, std - size_t.
Но это вообще не имеет никакого отношение к решению данной проблемы.

Цитировать
3) Пусть пакуются данные байты которых совпадают "через один", напр строки
abcdefgh
aacceegg
При этом упакованный размер намного превысит изначальный

Раздражает, да? Меня тоже :) Какая-то совершенно глупая задачка (явно не стоит выеденного яйца), а из нее все лезут и лезут проблемы, та шо такое  :'(

Ну так у вас 64 разрядная вроде система?
Тогда сделайте проверку сразу на блок символов по 8 байт. В вашем случае достаточно взять блок по 2 байта, чтобы уйти от этой проблемы.

Но я думаю вы надумали её. В ваших блоках хранятся совершенно конкретные данные, конкретных типов, и думаю, они все выравнены по 64 (32) бита, так что сравнение побайтово имеет, гм, чисто академический интерес :)

Но даже если отбросить эти теоретические выкладки, для хранения изменений вам нужно минимум 2 int  - итого 64 бита. Глупо записывать 1 байт данных на 8 байт метаданных, так что 64 таки блок делать. Или запихнуть метаданные в 1 int, тогда 32 байта блок.

Или решайте совсем универсально.
Определите эффективный размер блока.
Создайте матрицу, которая содержит изменения блоков (да, нет)
Объедините блоки, которые рядом (опционально, зависит от размера блока)
Посчитайте размер с метаданными всего диффа. Если не устраивает (например 70% от общего), записывайте оригинал.


Название: Re: Сохранить "диффы"
Отправлено: Igors от Январь 27, 2020, 16:14
Qt - int, std - size_t.
Но это вообще не имеет никакого отношение к решению данной проблемы.
Имеет, и с этой (вроде мелкой - но гнусной) проблемкой я сталкиваюсь уже не раз. Делать "по честному" (все 64) хорошо пока это ничего не стоит. А вот здесь уже стоит, и что делать - хз. Как минимум нужны комменты что работает до 4Gb, может лучше assert.

Демагогия началась.
...
Тогда сделайте проверку
...
Или решайте совсем
...
Создайте матрицу..
Таких советов может дать массу каждый. Но Вы сами бы последовали хотя бы одному из них? :) Думаю что нет. Писать код не хотите, уже поняли что там работа кропотливая. Ладно, давайте по-другому

Вот эта возня с "паковкой" - она неизбежна или все же надумана? Если последнее - то где более "ызячное" решение (лично я его не вижу)


Название: Re: Сохранить "диффы"
Отправлено: Azazello от Январь 27, 2020, 17:39
Имеет, и с этой (вроде мелкой - но гнусной) проблемкой я сталкиваюсь уже не раз. Делать "по честному" (все 64) хорошо пока это ничего не стоит. А вот здесь уже стоит, и что делать - хз. Как минимум нужны комменты что работает до 4Gb, может лучше assert.

Та делаете везде 64. А при записи обрезайте до 32-х. Исправить потом запись в одном месте, если превысит (а оно не привысит) ничего не будет стоить.

Цитировать
Таких советов может дать массу каждый. Но Вы сами бы последовали хотя бы одному из них? :) Думаю что нет. Писать код не хотите, уже поняли что там работа кропотливая. Ладно, давайте по-другому

Каких советов. Да держите. Тестируйте сами и в std сами переводите.

Код:
#include <QCoreApplication>

#include <QCoreApplication>

#include <QBitArray>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QByteArray s1("Tksteeeeeeeeeeeeeeeeeeeeeeeeeeeeehhhhhhhhhhhhhhhhhhhhhhsdfsdfffffffffffffffffffffffff");
    QByteArray s2("Testeeeeeeeeeeeeeeeeeeefeeeeeeeeehhhhhhhhhhhhhhhhhhhhhhsdfsdfffffffffffffffffffffffff");

    struct diff {
        int startPos;
        int size;
        char *data;
    };

    QVector <diff> diffs;

    constexpr int BLOCK_SIZE = 8; //nice to store in degree 2
    const int dataSize = s1.size();

    //count of blocks
    int blockCount = (dataSize % BLOCK_SIZE == 0) ? dataSize / BLOCK_SIZE : dataSize / BLOCK_SIZE + 1;
    QBitArray diffVector(blockCount);  //std::bitset
    //fill vector
    for (int i = 0; i < blockCount; ++i) {
        int dataPos = i * blockCount;
        //in real word for perfomance need comapre in registers
        //for instance
        //warning  - need unbounding check!
        //if (((__int64)s1[dataPos] != (__int64)s2[dataPos]))
        //or memcmp
        if (s1.mid(dataPos,BLOCK_SIZE) != s2.mid(dataPos,BLOCK_SIZE))
            diffVector.setBit(i);
    }

    //optimize result vector - in this case union through the one block
    //does not matter if block size equal metadata size
    //bad idea if block size is more than metadata size
   //good idea if block size is less than metadata size
    for (int i = 2; i < diffVector.size();++i) {
        if (diffVector[i] && diffVector[i] == diffVector[i - 2])
            diffVector.setBit(i - 1);
    }


    int diffPos = -1;
    //calc diffs
    for (int i = 0; i < diffVector.size(); ++i) {
       if (diffVector[i]) {
           if (diffPos == -1)
                diffPos = i * BLOCK_SIZE;
       } else {
           if (diffPos != -1) {
               int size = i * BLOCK_SIZE - diffPos;
               diffs.append({diffPos,size,s2.data() + diffPos});
               diffPos = -1;
           }
       }
    }

    //diff  in last element(s)
    if (diffPos != -1) {
        int size = dataSize - diffPos;
        diffs.append({diffPos,size,s2.data() + diffPos});
    }


    //calc diff size
    int diffSize = 0;
    for (int i = 0; i < diffs.size(); ++i) {
        diffSize += diffs[i].size + 8;
    }

    //nothing to do - diff more that 70% of the source size
    double fillPercent = diffSize / (double)dataSize;
    if (fillPercent > 0.7) {
        return 0;
    }

    //DO SAVE

    return a.exec();
}


Создайте тесты и на своих рабочих данных гоняйте их, изменяя например размер блока и данные, чтобы оценить эффективность. Никто в этом мире на ваших данных не подскажет вам параметры.

Это как размер кластера на винчестере. Рекомендации то у всех есть, а определять самому надо.
Да и вообще подходов может быть много. Вы же можете хранить данные в памяти с идентификацией объекта. А объект сам знает, чем он отличается от другого объекта.

Можно цепочку создавать. Я так понимаю, данные кусками идут повторяющиеся.
Ну, можно сравнить с видео - предыдущий кадр практически совпадает с текущим. Пока не появится новая сцена, тогда нужно новую цепочку строить. Это мои предположения, т.к. мы не знаем ваши условия.

P.S. Подправил исходники, неверно размер вычислялся


Название: Re: Сохранить "диффы"
Отправлено: Igors от Январь 28, 2020, 11:20
Каких советов. Да держите. Тестируйте сами и в std сами переводите.
Спасибо конечно, но что держать-то?  :)
Цитировать
Вот тебе швейная машинка - почини и строчи сколько хочешь
"DO SAVE" (что Вы видимо оставили мне для самостоятельной работы) - не простая формальность. Пример
Цитировать
abcdefghabcdefgh
abcdefg11bcdefgh
Нужно сохранить 10 байт (8 заголовок + 2 данные), а не 24. Конечно Вы не обязаны давать рабочий пример, ну и ладно, поговорим по коду

1)
Код:
    struct diff {
        int startPos;
        int size;
        char *data;
    };
Зачем хранить data если известно смещение в оригинале?

2)
Код:
QBitArray diffVector(blockCount);  //std::bitset
Зачем здесь экономить биты снижая скорость (пусть немного)? Простецкий vector<char> лучше

3)
Код:
       //in real word for perfomance need comapre in registers
        //for instance
        //warning  - need unbounding check!
        //if (((__int64)s1[dataPos] != (__int64)s2[dataPos]))
        //or memcmp
        if (s1.mid(dataPos,BLOCK_SIZE) != s2.mid(dataPos,BLOCK_SIZE))
            diffVector.setBit(i);
Батенька, ну это же "lapsus manus" (как говорили древние). Правда такой код потом ускорять хорошо, счет пойдет не на % а на разы. Наверно в этом была идея.

4)
Код:
   //nothing to do - diff more that 70% of the source size
    double fillPercent = diffSize / (double)dataSize;
    if (fillPercent > 0.7) {
        return 0;
Не понял Вашу задумку. Пакованные данные (по сути та же байт-паковка) имеют формат
Цитировать
header (fixed size)
data (var size)
Отсюда вытекает что "расстояние" между записываемыми байтами в оригинале должно быть >= header_size. Пример
Цитировать
abcdefgh
1bcdefg2
Нужно писать 16 байт (8 заголовок + 8 данные), хотя только 2 байта различны. Иначе мы потратим 8 + 1 + 8 + 1. Пусть ВСЕ байты разные - ну и ладно, значит будет 1 блок (копия 2-го исходного блока) + 8 байт на header накладные расходы, переживем. Поэтому упаковка cостоится "при любой погоде". И никакой свободы с выбором "размера блока" нет - он тупо равен размеру заголовка.

5) Отсюда единственная возможность как-то усилить алгоритм - как-то сократить размер заголовка. Подозреваю что оптимальным будет размер 4 (3 байта на смещение, до 16 Mb + 1 байт на размер). Но однобайтовый счетчик будет переполняться, придется создавать больше блоков. Накладные расходы приемлемы (до 2%), но возня.. я не решился на этот вариант, тупо заточил на header_size =8

Да, и конечно мой вариант (аттач), а то нехорошо с моей стороны. Обратите внимание что контейнеры (обычно такие удобные) здесь только мешают. Единственный вектор и то наверно лучше снести. "Разбрасывать носки" тоже надо уметь  :)

Можно цепочку создавать. Я так понимаю, данные кусками идут повторяющиеся.
Ну, можно сравнить с видео - предыдущий кадр практически совпадает с текущим. Пока не появится новая сцена, тогда нужно новую цепочку строить. Это мои предположения, т.к. мы не знаем ваши условия.
Любопытство может быть легко удовлетворено - это данные skin деформаций. Для каждой "кости" (60 и более на объект) записываются все индексы + вес всех вертексов на которые влияет эта кость + матрицы трансформа. Вот и объем прет. Ну теперь Вы знаете "мои условия" - это как-то помогло?  :)




Название: Re: Сохранить "диффы"
Отправлено: Azazello от Январь 28, 2020, 21:17
Вы наверное прикалуетесь. Или Вам общения не хватает. Мыслей у меня умней нет.
Возможно, Вы хотите просто поговорить. Или постебаться.

Вот тебе швейная машинка - почини и строчи сколько хочешь.
"DO SAVE" (что Вы видимо оставили мне для самостоятельной работы) - не простая формальность. Пример
Ай. Извиняюсь. Зарылся и пошел писать и тестировать код. Через месяц встретимся.
Всё для Вас.
Особенно порадовало самостаятельная работа в одну строку.
qDebug() << diff.startPos << diff.QByteArray(diff.data,diff.size);
Где qDebug заменяется на поток записи в файл.

Цитировать
Зачем хранить data если известно смещение в оригинале?

Зачем здесь экономить биты снижая скорость (пусть немного)? Простецкий vector<char> лучше

Батенька, ну это же "lapsus manus" (как говорили древние). Правда такой код потом ускорять хорошо, счет пойдет не на % а на разы. Наверно в этом была идея.

Вы понимаете о чём Вы говорите?
Ваша задача оптимизировать объем записи. Зачем вообще думать о производительности?
Какая разница char* или bit. Вы я так понял человек процесса, а не результата.
Зачем отвлекаться на незначимые вещи, которые не имеют никакого значения?
Ну сделайте чтобы оно хоть как-то работало медленно, а вопрос производительсти решается на ура потом. Главное восприятие и понятность.
Но нет, оно не работает нифига, зато нужно рассуждать о задаче, которая разбита на этапы (чтобы лучше воспринималась) и рассуждать о производительности.

Делаем машину. Она не едет, зато рассуждения об оббивке сидений.

Кстати, что за фамильярность,
Цитировать
Батенька?
Вы же у нас тут "Облико морале".

О чем мы с Вами можем разговаривать, когда мы друг друга не слышим. Я хотя бы пытаюсь.
Это разговор двух идиотов.

Не хочу больше рассуждать об этом.
Закончу, что я имел в виду, а Вы не поняли: универсальный метод типа RLE  вам не грозит в связи с малым объемом данных. Хотите пробовать - протестируте, ок если я ошибаюсь.
 Значит нужно искать свой гребаный путь для своих данных  - находить пакеты отностительно похожих данных с одинаковой границей.

Люди на таких задачах только на тестовых данных недели тратят. НЕ! Швейную машинку ему дали не ту.
Недели и месяцы, чтобы просто проаналзировать, что происходит и только потом голову чухают, а как же его сделать?

"Боюсь за границы 4Gb вылезти при использовании int". Прикольный save на 4 Гб. Если вы её эффективно решите, можете забить на свою программу, вы уже миллионэр.
Не отвечайте на данный пост.

P.S. Не удержался.
Хотите алгоритм - вот вам.
1. Сохраняете свои блоки, при разных условия, мышкой водите там или чего. Короче для разных процессов.
2. Пишите тулузу, которая читает эти данные и визуализиует для Вас. Аля дефрагментор как раньше показывал. Синенькие совпадаю, красненькие жопас.
3. Думаете.
4. Находите почему так и что-то общее.
5. Прозреваете либо забиваете.
6. Задумываетесь.

P.S.S. Не отвечайте.


Название: Re: Сохранить "диффы"
Отправлено: Igors от Январь 29, 2020, 08:59
Возможно, Вы хотите просто поговорить.
Да, и думаю все присутствующие тоже. Только полный идиот надеется что на форуме (любом) будут ему "памагать". Это типа утренней газеты, которую можно прочитать с интересом и пользой (узнав мнение др людей). Ну не всегда конечно
Или постебаться.
Нет

Да, и Вы мне помогли. Вариант что я выложил написан уже после общения с Вами, он не первый и даже не второй. Те были очень похожи на Ваши. И вот мне стало интересно - ну почему такая сопливая задачка выливается в нечто ужасное? Думаю что "довлеет" накопленный опыт. Напр мы интуитивно (или рефлекторно) лепим контейнеры (ну а как иначе?) и хотим с них что-то "наварить". Как только мне удалось от этого избавиться (не без труда) - все легло вполне прилично. Ведь это просто типа студенческая лаба.

Спасибо


Название: Re: Сохранить "диффы"
Отправлено: Авварон от Январь 30, 2020, 22:08
Напр мы интуитивно (или рефлекторно) лепим контейнеры (ну а как иначе?) и хотим с них что-то "наварить". Как только мне удалось от этого избавиться (не без труда) - все легло вполне прилично. Ведь это просто типа студенческая лаба.


Угу, только здесь нужны не контейнеры, а вью. Но вы пока не доросли, ведь "опыт контейнеров" и "студенческих либ" неискореним=)


Название: Re: Сохранить "диффы"
Отправлено: Igors от Январь 31, 2020, 11:48
Угу, только здесь нужны не контейнеры, а вью. Но вы пока не доросли, ведь "опыт контейнеров" и "студенческих либ" неискореним=)
Причем тут "вью" и что здесь имеется ввиду под этим общим словом - ума не приложу. Хорошо, допустим не дорос - развейте свою мысль (а то пока только лягаете испоттишка  :))


Название: Re: Сохранить "диффы"
Отправлено: Авварон от Январь 31, 2020, 17:54
Угу, только здесь нужны не контейнеры, а вью. Но вы пока не доросли, ведь "опыт контейнеров" и "студенческих либ" неискореним=)
Причем тут "вью" и что здесь имеется ввиду под этим общим словом - ума не приложу. Хорошо, допустим не дорос - развейте свою мысль (а то пока только лягаете испоттишка  :))

Ну вот же

Код:
struct CMemBlock {
char * mData;
UInt32 mSize;
};

Это уже давно (с++17) изобрели (https://en.cppreference.com/w/cpp/string/basic_string_view) за вас. Не нравится строка, возьмите span (https://en.cppreference.com/w/cpp/container/span) чаров.
А "контейнеры" тут действительно не нужны.
Просто их везде пихали по той причине, что работать с std::string удобнее, чем с голым char*.
Теперь есть удобная обертка над "сишным массивом", тягать голые указатели больше не надо.


Название: Re: Сохранить "диффы"
Отправлено: Igors от Февраль 01, 2020, 07:44
Это уже давно (с++17) изобрели за вас.
..
Теперь есть удобная обертка над "сишным массивом", тягать голые указатели больше не надо.
А, да, эта вещь давно "витала в воздухе", и я юзал не одну реализацию. Кстати когда я предложил одну из них здесь (см "Кладовая") - был подвергнут уничтожающей критике местных знатоков  :)

Только не вижу как это связано с темой. Вроде "вьюить" тут ничего и не надо. Более естественно объявить аргументы векторами - но это придется делать по всему старому коду, а его не мало. В конце-концов "примитивно" совсем не значит "плохо"  :)