Подтвердить что ты не робот

Почему LZMA SDK (7-zip) настолько медленный

Я нашел 7-zip отлично, и я хотел бы использовать его в приложениях .net. У меня есть файл размером 10 МБ (a.001), и он принимает:

enter image description here

2 секунды для кодирования.

Теперь будет хорошо, если бы я мог сделать то же самое на С#. Я загрузил http://www.7-zip.org/sdk.html Исходный код LZMA SDK С#. Я в основном копировал каталог CS в консольное приложение в visual studio: enter image description here

Затем я скомпилировал и eveything скомпилировал плавно. Поэтому в выходной директории я разместил файл a.001 размером 10 МБ. В основном методе, который пришел на исходный код, я разместил:

[STAThread]
    static int Main(string[] args)
    {
        // e stands for encode
        args = "e a.001 output.7z".Split(' '); // added this line for debug

        try
        {
            return Main2(args);
        }
        catch (Exception e)
        {
            Console.WriteLine("{0} Caught exception #1.", e);
            // throw e;
            return 1;
        }
    }

когда я запускаю консольное приложение, приложение отлично работает, и я получаю вывод a.7z в рабочем каталоге. Проблема в том, что это занимает много времени. Это займет около 15 секунд! Я также пробовал метод qaru.site/info/67202/..., и это также занимает очень много времени. Почему он в 10 раз медленнее, чем реальная программа?

Также

Даже если я решил использовать только один поток: enter image description here

По-прежнему требуется гораздо меньше времени (3 секунды против 15):


(Изменить) Другая возможность

Может быть, потому, что С# медленнее сборки или C? Я замечаю, что алгоритм выполняет много тяжелых операций. Например, сравните эти два блока кода. Они оба делают одно и то же:

С

#include <time.h>
#include<stdio.h>

void main()
{
    time_t now; 

    int i,j,k,x;
    long counter ;

    counter = 0;

    now = time(NULL);

    /* LOOP  */
    for(x=0; x<10; x++)
    {
        counter = -1234567890 + x+2;

        for (j = 0; j < 10000; j++)     
            for(i = 0; i< 1000; i++)                
                for(k =0; k<1000; k++)
                {
                    if(counter > 10000)
                        counter = counter - 9999;
                    else
                        counter= counter +1;
                }

        printf (" %d  \n", time(NULL) - now); // display elapsed time
    }


    printf("counter = %d\n\n",counter); // display result of counter        

    printf ("Elapsed time = %d seconds ", time(NULL) - now);
    gets("Wait");
}

Выход

enter image description here

С#

static void Main(string[] args)
{       
    DateTime now;

    int i, j, k, x;
    long counter;

    counter = 0;

    now = DateTime.Now;

    /* LOOP  */
    for (x = 0; x < 10; x++)
    {
        counter = -1234567890 + x + 2;

        for (j = 0; j < 10000; j++)            
            for (i = 0; i < 1000; i++)                
                for (k = 0; k < 1000; k++)
                {
                    if (counter > 10000)
                        counter = counter - 9999;
                    else
                        counter = counter + 1;
                }


        Console.WriteLine((DateTime.Now - now).Seconds.ToString());            
    }

    Console.Write("counter = {0} \n", counter.ToString());
    Console.Write("Elapsed time = {0} seconds", DateTime.Now - now);
    Console.Read();
}

Выход

enter image description here

Обратите внимание, насколько медленнее был С#. Обе программы, которые запускаются из-за пределов визуальной студии в режиме выпуска. Возможно, именно по этой причине это занимает намного больше времени в .net, чем на С++.

Также я получил те же результаты. С# был в 3 раза медленнее, как на примере, который я только что показал!


Заключение

Я не могу понять, что вызывает проблему. Думаю, я буду использовать 7z.dll и вызывать необходимые методы из С#. Библиотека, которая делает это: http://sevenzipsharp.codeplex.com/ и таким образом я использую ту же библиотеку, которую использует 7zip как:

    // dont forget to add reference to SevenZipSharp located on the link I provided
    static void Main(string[] args)
    {
        // load the dll
        SevenZip.SevenZipCompressor.SetLibraryPath(@"C:\Program Files (x86)\7-Zip\7z.dll");

        SevenZip.SevenZipCompressor compress = new SevenZip.SevenZipCompressor();

        compress.CompressDirectory("MyFolderToArchive", "output.7z");


    }
4b9b3361

Ответ 1

Этот тип двоично-арифметического и разветвляющегося кода - это то, что любят C-компиляторы и что ненавидит .NET JIT..NET JIT не очень умный компилятор. Он оптимизирован для быстрой компиляции. Если Microsoft захочет настроить его на максимальную производительность, они будут подключаться к серверу VС++, но затем намеренно этого не делают.

Кроме того, я могу сказать по скорости, которую вы получаете с 7z.exe(6 МБ/с), что вы используете несколько ядер, возможно, используя LZMA2. Мой быстрый ядро ​​i7 может доставлять 2 Мбайт/с на ядро, поэтому я думаю, что 7z.exe работает многопоточно для вас. Попробуйте включить threading в 7zip-библиотеке, если это возможно.

Я рекомендую, чтобы вместо использования LZMA-алгоритма управляемого кода вы либо использовали изначально скомпилированную библиотеку, либо вызывали 7z.exe с помощью Process.Start. Последний должен заставить вас начать очень быстро с хорошими результатами.

Ответ 2

Я запустил профайлер кода, и самая дорогая операция, похоже, находится в поиске совпадений. В С# он ищет один байт за раз. В LzBinTree.cs есть две функции (GetMatches и Skip), которые содержат следующий фрагмент кода, и он тратит на этот код примерно 40-60% своего времени:

if (_bufferBase[pby1 + len] == _bufferBase[cur + len])
{
    while (++len != lenLimit)
        if (_bufferBase[pby1 + len] != _bufferBase[cur + len])
            break;

В основном он пытается найти длину совпадения по одному байту за раз. Я извлек это в свой собственный метод:

if (GetMatchLength(lenLimit, cur, pby1, ref len))
{

И если вы используете небезопасный код и отбрасываете байт * на ulong * и сравниваете 8 байтов за один раз вместо 1, скорость почти удваивается для моих тестовых данных (в 64-битном процессе):

private bool GetMatchLength(UInt32 lenLimit, UInt32 cur, UInt32 pby1, ref UInt32 len)
{
    if (_bufferBase[pby1 + len] != _bufferBase[cur + len])
        return false;
    len++;

    // This method works with or without the following line, but with it,
    // it runs much much faster:
    GetMatchLengthUnsafe(lenLimit, cur, pby1, ref len);

    while (len != lenLimit
        && _bufferBase[pby1 + len] == _bufferBase[cur + len])
    {
        len++;
    }
    return true;
}

private unsafe void GetMatchLengthUnsafe(UInt32 lenLimit, UInt32 cur, UInt32 pby1, ref UInt32 len)
{
    const int size = sizeof(ulong);
    if (lenLimit < size)
        return;
    lenLimit -= size - 1;
    fixed (byte* p1 = &_bufferBase[cur])
    fixed (byte* p2 = &_bufferBase[pby1])
    {
        while (len < lenLimit)
        {
            if (*((ulong*)(p1 + len)) == *((ulong*)(p2 + len)))
            {
                len += size;
            }
            else
                return;
        }
    }
}

Ответ 3

Я сам не использовал LZMA SDK, но я уверен, что по умолчанию 7-zip запускает большую часть операций над многими потоками. Поскольку я не сделал этого сам, единственное, что я могу предложить, - проверить, можно ли заставить его использовать много потоков (если оно не используется по умолчанию).

Изменить:


Похоже, что потоки могут не быть (единственной) проблемой, связанной с производительностью, есть и другие, о которых я мог подумать:
  • Вы проверили, что вы установили те же самые параметры, что и при использовании 7-zip-интерфейса? Является ли выходной файл того же размера? Если нет - может случиться, что один метод сжатия намного быстрее, чем другой.

  • Выполняется ли ваше приложение с VS или нет? Если это так - это может также добавить некоторые накладные расходы (но я думаю, это не должно приводить к тому, что приложение работает в 5 раз медленнее).

  • Есть ли какие-либо другие операции перед сжатием файла?

Ответ 4

Я только что рассмотрел реализацию LZMA CS, и все это выполнялось в управляемом коде. Недавно сделав некоторое расследование этого требования сжатия для моего текущего проекта, большинство реализаций сжатия в управляемом коде, похоже, работают менее эффективно, чем в native.

Я могу только предположить, что это является причиной проблемы здесь. Если вы посмотрите таблицу производительности другого инструмента сжатия QuickLZ, вы увидите разницу в производительности между собственным и управляемым кодом (будь то С# или Java).

Приходят на ум два варианта: используйте средства взаимодействия с .NET, чтобы вызвать собственный метод сжатия, или если вы можете позволить себе жертвовать размер сжатия, посмотрите http://www.quicklz.com/.

Ответ 5

Другой альтернативой является использование SevenZipSharp (доступно на NuGet) и указание его на ваш 7z.dll. Тогда ваши скорости должны быть примерно одинаковыми:

var libPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "7-zip", "7z.dll");
SevenZip.SevenZipCompressor.SetLibraryPath(libPath);
SevenZip.SevenZipCompressor compressor = new SevenZipCompressor();
compressor.CompressFiles(compressedFile, new string[] { sourceFile });

Ответ 6

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

Это неявные инструкции перед каждой инструкцией, которая замедляет время выполнения .net. В типичных бизнес-приложениях мы не заботимся о производительности, где важна сложность бизнес-логики и ui-логики, поэтому время выполнения .net защищает каждую инструкцию с особой осторожностью, что позволяет нам быстро отлаживать и разрешать проблемы.

Нативные программы c всегда будут быстрее, чем время выполнения .net, но их трудно отлаживать и нужно углубленно знать c для написания правильного кода. Потому что c выполнит все, но не даст вам никаких исключений или подсказок о том, что пошло не так.