web-dev-qa-db-ja.com

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ディレクトリをVisualStudioのコンソールアプリケーションにコピーしました。 enter image description here

それから私はコンパイルし、すべてがスムーズにコンパイルされました。そのため、出力ディレクトリに、10MBのサイズのファイルa.001を配置しました。私が配置したソースコードに付属するメインメソッドについて:

[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倍遅いのはなぜですか?

また

1つのスレッドのみを使用するように設定した場合でも: enter image description here

それでもはるかに短い時間で済みます(3秒対15):


(編集)別の可能性

C#がアセンブリまたはCよりも遅いためでしょうか?アルゴリズムが多くの重い操作を行うことに気づきました。たとえば、これら2つのコードブロックを比較します。それらは両方とも同じことをします:

[〜#〜] 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

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)\7-Zip\7z.dll");

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

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


    }
23
Tono Nam

この種のバイナリ算術および分岐の多いコードは、Cコンパイラが好きで、.NETJITが嫌いなものです。 .NET JITは、あまりスマートなコンパイラではありません。高速コンパイル用に最適化されています。 Microsoftが最大のパフォーマンスを実現するように調整したい場合は、VC++バックエンドをプラグインしますが、意図的にプラグインしません。

また、7z.exe(6MB/s)で得られる速度から、おそらくLZMA2を使用して複数のコアを使用していることがわかります。私の高速コアi7はコアあたり2MB /秒を提供できるので、7z.exeはマルチスレッドで実行されていると思います。可能であれば、7Zipライブラリでスレッドをオンにしてみてください。

マネージコードLZMAアルゴリズムを使用する代わりに、ネイティブにコンパイルされたライブラリを使用するか、Process.Startを使用して7z.exeを呼び出すことをお勧めします。後者は、すぐに始めて良い結果が得られるはずです。

8
usr

コードでプロファイラーを実行しましたが、最もコストのかかる操作は一致するものを検索することであるようです。 C#では、一度に1バイトを検索します。 LzBinTree.csには次のコードスニペットを含む2つの関数(GetMatchesとSkip)があり、このコードに40〜60%の時間を費やしています。

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

基本的に、一度に1バイトずつ一致する長さを見つけようとします。私はそれを独自の方法に抽出しました:

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

また、安全でないコードを使用してバイト*をulong *にキャストし、一度に1バイトではなく8バイトを比較すると、テストデータの速度はほぼ2倍になります(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;
        }
    }
}
11
Bryce Wagner

私自身はLZMASDKを使用していませんが、デフォルトでは7-Zipが多くのスレッドでほとんどの操作を実行していると確信しています。私は自分でそれを行っていないので、私が提案する唯一のことは、それが多くのスレッドを使用するように強制することが可能かどうかを確認することです(デフォルトで使用されていない場合)。

編集:


  1. 7-Zip UIを使用するときに設定するのとまったく同じオプションを設定したことを確認しましたか?出力ファイルは同じサイズですか?そうでない場合、一方の圧縮方法がもう一方の圧縮方法よりもはるかに高速である可能性があります。

  2. VSを使用せずにアプリケーションを実行していますか?もしそうなら-これもオーバーヘッドを追加する可能性があります(しかし、アプリの実行速度が5倍遅くなることはないと思います)。

  3. ファイルを圧縮する前に他の操作が行われていますか?
3
MaciekTalaska

LZMA CSの実装を見たところ、すべてマネージコードで実行されています。私の現在のプロジェクトの圧縮要件について最近調査を行ったところ、マネージコードでの圧縮のほとんどの実装はネイティブよりも効率が悪いようです。

これが問題の原因であると推測できます。別の圧縮ツールであるQuickLZのパフォーマンス表を見ると、ネイティブコードとマネージコード(C#またはJava)のパフォーマンスの違いがわかります。

2つのオプションが思い浮かびます。NETの相互運用機能を使用してネイティブの圧縮メソッドを呼び出すか、圧縮サイズを犠牲にする余裕がある場合は、 http://www.quicklz.com/ を参照してください。

3
pixelbadger

もう1つの方法は、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 });
3
Ed Power

.netランタイムは、ネイティブ命令よりも低速です。 cで問題が発生した場合、通常、ブルースクリーンでアプリケーションがクラッシュします。しかし、c#では、cで行わないチェックはすべて、実際にはc#に追加されるため、そうではありません。 nullの追加チェックを行わないと、ランタイムはnullポインター例外をキャッチできません。インデックスと長さをチェックしないと、ランタイムは範囲外の例外をキャッチできません。

これらは、.netランタイムを遅くするすべての命令の前の暗黙の命令です。通常のビジネスアプリでは、ビジネスの複雑さとUIロジックがより重要であるパフォーマンスを気にしません。そのため、.netランタイムは、問題のデバッグと解決を迅速に行えるように細心の注意を払ってすべての命令を保護します。

ネイティブcプログラムは、常に.netランタイムよりも高速ですが、デバッグが難しく、正しいコードを記述するためにcに関する深い知識が必要です。 cはすべてを実行しますが、何が悪かったのかについての例外や手がかりを与えることはありません。

2
Akash Kava