почему LZMA SDK (7-zip) так медленно

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

enter image description here

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

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

затем я скомпилировал и все прошло гладко. Поэтому в выходной каталог я поместил файл a.001 что 10MB размера. На основном методе, который пришел на исходный код, я разместил:

[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 секунд, чтобы выполнить! Я также пробовал https://stackoverflow.com/a/8775927/637142 подход и это также занимает очень много времени. Почему он в 10 раз медленнее, чем сама программа ?

и

даже если я использую только один поток: enter image description here

это все еще занимает гораздо меньше времени (3 секунды против 15):


(Edit) Еще Одна Возможность

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

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 = %dnn",counter); // display result of counter        

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

выход

enter image description here

c#

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

обратите внимание, насколько медленнее был c#. Обе программы, запущенные из-за пределов visual studio в режиме выпуска. возможно, именно поэтому в .net это занимает гораздо больше времени, чем на c++.

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


вывод

Я, похоже, не знаю, что вызывает проблему. Я думаю, я буду использовать 7z.dll и вызовите необходимые методы из c#. Библиотека, которая делает это на: 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)-Zipz.dll");

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

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


    }

6 ответов


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

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

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


я запустил профилировщик кода, и самая дорогая операция, похоже, заключается в поиске совпадений. В C# он ищет по одному байту за раз. В LzBinTree есть две функции (GetMatches и Skip).cs, которые содержат следующий фрагмент кода, и он тратит что-то вроде 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;
        }
    }
}

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

изменить:


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

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

  3. есть ли какие-либо другие операции, происходящие перед сжатием файл?

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

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

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


Другой альтернативой является использование 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 });

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

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

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