代码之家  ›  专栏  ›  技术社区  ›  Trap

为什么在这个例子中使用浮点数会使我比使用双倍点数慢2倍?

  •  3
  • Trap  · 技术社区  · 17 年前

    我最近一直在做一些分析,我遇到了一个让我发疯的案例。下面是一段不安全的C#代码,它基本上以不同的采样率将源样本缓冲区复制到目标缓冲区。现在,它每帧占用约0.17%的总处理时间。我不明白的是,如果我使用浮动而不是双精度,处理时间将提高到0.38%。有人能解释一下这是怎么回事吗?

    快速版本(~17%)

    double rateIncr = ...
    double readOffset = ...
    double offsetIncr = ...
    
    float v = ... // volume
    
    // Source and target buffers.
    float* src = ...
    float* tgt = ...
    
    for( var c = 0; c < chunkCount; ++c)
    {
        for( var s = 0; s < chunkSampleSize; ++s )
        {
            // Source sample            
            var iReadOffset = (int)readOffset;
    
            // Interpolate factor
            var k = (float)readOffset - iReadOffset;
    
            // Linearly interpolate 2 contiguous samples and write result to target.
            *tgt++ += (src[ iReadOffset ] * (1f - k) + src[ iReadOffset + 1 ] * k) * v;
    
            // Increment source offset.
            readOffset += offsetIncr;
        }
        // Increment sample rate
        offsetIncr += rateIncr;
    }
    

    float rateIncr = ...
    float readOffset = ...
    float offsetIncr = ...
    
    float v = ... // volume
    
    // Source and target buffers.
    float* src = ...
    float* tgt = ...
    
    for( var c = 0; c < chunkCount; ++c)
    {
        for( var s = 0; s < chunkSampleSize; ++s )
        {
            var iReadOffset = (int)readOffset;
    
            // The cast to float is removed
            var k = readOffset - iReadOffset;
    
            *tgt++ += (src[ iReadOffset ] * (1f - k) + src[ iReadOffset + 1 ] * k) * v;
            readOffset += offsetIncr;
        }
        offsetIncr += rateIncr;
    }
    

    奇数版本(~22%)

    float rateIncr = ...
    float readOffset = ...
    float offsetIncr = ...
    
    float v = ... // volume
    
    // Source and target buffers.
    float* src = ...
    float* tgt = ...
    
    for( var c = 0; c < chunkCount; ++c)
    {
        for( var s = 0; s < chunkSampleSize; ++s )
        {
            var iReadOffset = (int)readOffset;
            var k = readOffset - iReadOffset;
    
            // By just placing this test it goes down from 38% to 22%,
            // and the condition is NEVER met.
            if( (k != 0) && Math.Abs( k ) < 1e-38 )
            {
               Console.WriteLine( "Denormalized float?" );
            }
    
            *tgt++ += (src[ iReadOffset ] * (1f - k) + src[ iReadOffset + 1 ] * k) * v;
            readOffset += offsetIncr;
        }
        offsetIncr += rateIncr;
    }
    

    我现在只知道我什么都不知道

    7 回复  |  直到 17 年前
        1
  •  4
  •   Martin Harris    17 年前

    您是在64位还是32位处理器上运行此功能?我的经验是,在某些边缘情况下,如果对象的大小与寄存器的大小相匹配,CPU可以通过这样的低级别功能进行优化(即使您可能假设两个浮点数可以整齐地装入64位寄存器,您仍可能失去优化的好处)。如果在32位系统上运行,您可能会发现情况正好相反。。。

    一个快速的搜索和我能做的最好的引用是在C++游戏开发论坛上的几篇文章(我在游戏DVI的一年里我自己注意到了,但是那是我唯一的时间来分析这个级别)。 This post 从C++方法中得到一些有趣的拆卸结果,这些方法可能适用于非常低的水平。


    另一个想法是:

    This article 从MSDN开始,我们讨论了在.NET中使用浮点的许多内部细节,主要是为了解决浮点比较的问题。其中有一个有趣的段落总结了处理浮点值的CLR规范:

    FPU。规范基本上是这样说的 允许使用CLR实现 大小写,x87 80位表示) 只要没有明确的 或valuet类型字段),强制 缩小。而且,在任何时候,IL 流可能有conv.r4和conv.r8 指令,这将强制 正在缩小范围。

        2
  •  3
  •   ConcernedOfTunbridgeWells    17 年前

    可能在某个地方发生了一系列双浮转换,占用了CPU时间。你能用IL反汇编程序查看输出,看看它实际在做什么吗?

        3
  •  2
  •   Frederik Slijkerman    17 年前

    您的计算可能会导致浮点值进入“非规范”状态,这在大多数x86处理器上是非常低效的。非规范值非常小,以至于它们位于最小可能浮点值的边缘。相比之下,这样的值可以很好地适应双重范围,因此在这种情况下,计算是有效的。

    http://en.wikipedia.org/wiki/Denormal

        4
  •  1
  •   Curt Hagenlocher    17 年前

    如果执行此操作,您可能希望先启动程序,然后再附加调试器,以便不会禁用JIT优化。毕竟,您希望确保查看的是实际要运行的代码。

        5
  •  1
  •   cyberconte    17 年前

    考虑到您的大部分代码是 处理在double和float之间切换的3个变量,以及性能上的较大变化,我认为类型和测试中的微小变化足以改变缓存占用空间和/或寄存器使用。

    我在我的32位机器上做了一些快速测试:

    // NOTE: runnable - copy in paste into your own project
    class Program
        {
            static int endVal = 32768;
            static int runCount = 100;
            static void Main(string[] args)
            {
                Stopwatch doublesw = Stopwatch.StartNew();
                for (int i = 0; i < runCount; ++i)
                    doubleTest();
                doublesw.Stop();
                Console.WriteLine("Double: " + doublesw.ElapsedMilliseconds);
                Stopwatch floatsw = Stopwatch.StartNew();
                for (int i = 0; i < runCount; ++i)
                    floatTest();
                floatsw.Stop();
                Console.WriteLine("Float: " + floatsw.ElapsedMilliseconds);
                Console.ReadLine();
            }
    
            static void doubleTest()
            {
                double value = 0;
                double incr = 0.001D;
    
                while (value < endVal)
                {
                    value += incr;
                }
            }
    
            static void floatTest()
            {
                float value = 0;
                float incr = 0.001f;
    
                while (value < endVal)
                {
                    value += incr;
                }
            }
        }
    }
    

    结果是:

    Double: 12897
    Float: 10059
    

    不幸的是,您提供的代码中缺少了足够多的部分,因此我无法很好地编译和读取程序集,以查看到底发生了什么,但从我的(快速)测试来看,这是我的答案。

        6
  •  0
  •   leppie    17 年前

    (float)readOffset
    

    尝试使readOffset也浮动。

        7
  •  0
  •   Oliver    17 年前

    如果在函数内部使用浮动,而在外部使用一些双倍,则需要一些时间进行转换,这意味着内部函数本身的百分比时间会下降,因为函数本身的处理时间是恒定的,整个过程需要更多的时间。