24

我发现 7-zip 很棒,我想在 .net 应用程序上使用它。我有一个 10MB 的文件(a.001),它需要:

在此处输入图像描述

2 秒编码

现在,如果我能在 c# 上做同样的事情,那就太好了。我已经下载了http://www.7-zip.org/sdk.html LZMA SDK c# 源代码。我基本上将 CS 目录复制到了 Visual Studio 中的控制台应用程序中: 在此处输入图像描述

然后我编译并顺利编译。因此,在输出目录中,我放置了a.00110MB 大小的文件。在我放置的源代码中的主要方法上:

[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 倍?

即使我设置只使用一个线程: 在此处输入图像描述

它仍然需要更少的时间(3 秒对 15 秒):


(编辑)另一种可能性

可能是因为 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 = %d\n\n",counter); // display result of counter        

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

输出

在此处输入图像描述

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();
}

输出

在此处输入图像描述

注意 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");


    }
4

6 回答 6

11

我在代码上运行了一个分析器,最昂贵的操作似乎是搜索匹配项。在 C# 中,它一次搜索一个字节。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))
{

如果您使用不安全的代码并将 byte* 转换为 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;
        }
    }
}
于 2013-07-11T16:14:14.153 回答
9

这种二进制算术和分支繁重的代码是 C 编译器喜欢的,而 .NET JIT 讨厌的。.NET JIT 不是一个非常聪明的编译器。它针对快速编译进行了优化。如果微软想调整它以获得最大性能,他们会插入 VC++ 后端,但后来故意不这样做。

此外,我可以通过您使用 7z.exe (6MB/s) 获得的速度来判断您正在使用多个内核,可能使用的是 LZMA2。我的快速核心 i7 可以提供每个核心 2MB/s 的速度,所以我猜 7z.exe 正在为您运行多线程。如果可能,请尝试在 7zip 库中打开线程。

我建议您不要使用托管代码 LZMA 算法,而是使用本机编译的库或使用Process.Start. 后一个应该让你很快开始并取得良好的效果。

于 2012-09-13T19:10:24.927 回答
3

Another alternative is to use SevenZipSharp (available on NuGet) and point it to your 7z.dll. Then your speeds should be about the same:

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 });
于 2012-09-14T21:42:45.333 回答
3

我自己没有使用过 LZMA SDK,但我很确定默认情况下 7-zip 在许多线程上运行大部分操作。由于我自己没有这样做,我唯一建议的是检查是否可以强制它使用多个线程(如果默认情况下不使用它)。

编辑:


似乎线程可能不是(唯一的)与性能相关的问题,我还可以想到其他一些问题:

  1. 您是否检查过您设置的选项与使用 7-zip UI 时设置的选项完全相同?输出文件的大小是否相同?如果不是 - 可能会发生一种压缩方法比另一种压缩方法快得多。

  2. 您是否通过 VS 执行您的应用程序?如果是这样 - 这也可能会增加一些开销(但我想它不应该导致应用程序运行速度慢 5 倍)。

  3. 在压缩文件之前是否有任何其他操作发生?
于 2012-09-06T03:45:37.070 回答
3

我刚刚查看了 LZMA CS 实现,它都是在托管代码中执行的。最近针对我当前项目的压缩要求对此进行了一些调查,托管代码中的大多数压缩实现似乎比本机代码执行效率低。

我只能假设这是这里问题的原因。如果您查看另一个压缩工具 QuickLZ 的性能表,您可以看到本机代码和托管代码(无论是 C# 还是 Java)之间的性能差异。

想到两个选项:使用 .NET 的互操作工具调用本机压缩方法,或者如果您可以牺牲压缩大小,请查看http://www.quicklz.com/

于 2012-09-13T18:44:47.027 回答
2

.net 运行时比本机指令慢。如果 c 中出现问题,我们通常会导致应用程序崩溃并蓝屏死机。但在 c# 中却没有,因为我们没有在 c 中进行的任何检查实际上都添加到了 c# 中。如果不对 null 进行额外检查,运行时将永远无法捕获空指针异常。如果不检查索引和长度,运行时永远无法捕获越界异常。

这些是使 .net 运行时变慢的每条指令之前的隐式指令。在典型的业务应用程序中,我们不关心性能,其中业务和 ui 逻辑的复杂性更为重要,这就是为什么 .net 运行时会格外小心地保护每条指令,以便我们快速调试和解决问题。

原生 c 程序总是比 .net 运行时更快,但它们很难调试,并且需要深入了解 c 才能编写正确的代码。因为 c 会执行一切,但不会给你任何异常或出错的线索。

于 2012-09-14T20:23:57.763 回答