当使用Math.Round转换double为int时,我注意到了一个非常显着的(〜15x)的性能下降,而x64与x86相比.我在Core i7 3770K上的64位 Windows上进行了测试.有人可以重现吗?有没有什么好的理由呢?也许有些奇怪的边界条件?

仅供参考,我将Math.Round(Test1)与2个近似值进行了比较:条件转换(Test2)和6755399441055744技巧(Test3).

运行时间为:

---------------------------
|       |   x86  |  x64   |
|-------+--------+--------|
| Test1 | 0,0662 | 0,9975 |
| Test2 | 0,1517 | 0,1513 |
| Test3 | 0,1966 | 0,0978 |
---------------------------

以下是基准代码:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace MathRoundTester
{
    class Program
    {
        private const int IterationCount = 1000000;

        private static int dummy;
        static void Main(string[] args)
        {
            var data = new double[100];
            var rand = new Random(0);
            for (int i = 0; i < data.Length; ++i)
            {
                data[i] = rand.NextDouble() * int.MaxValue * 2 +
                    int.MinValue + rand.NextDouble();
            }

            dummy ^= Test1(data);
            dummy ^= Test2(data);
            dummy ^= Test3(data);
            RecordTime(data,Test1);
            RecordTime(data,Test2);
            RecordTime(data,Test3);
            Console.WriteLine(dummy);
            Console.Read();
        }
        private static void RecordTime(double[] data,Func<double[],int> action)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            var sw = Stopwatch.StartNew();
            dummy ^= action(data);
            sw.Stop();
            Console.WriteLine((sw.ElapsedTicks / (double)Stopwatch.Frequency).ToString("F4"));
        }
        private static int Test1(double[] data)
        {
            int d = 0;
            for (int i = 0; i < IterationCount; ++i)
            {
                for (int j = 0; j < data.Length; ++j)
                {
                    var x = data[j];
                    d ^= (int)Math.Round(x);
                }
            }
            return d;
        }
        private static int Test2(double[] data)
        {
            int d = 0;
            for (int i = 0; i < IterationCount; ++i)
            {
                for (int j = 0; j < data.Length; ++j)
                {
                    var x = data[j];
                    d ^= x > 0 ? (int)(x + 0.5) : (int)(x - 0.5);
                }
            }
            return d;
        }
        [StructLayout(LayoutKind.Explicit)]
        private struct DoubleIntUnion
        {
            public DoubleIntUnion(double a)
            {
                Int = 0;
                Double = a;
            }
            [FieldOffset(0)]
            public double Double;
            [FieldOffset(0)]
            public int Int;
        }
        private static int Test3(double[] data)
        {
            int d = 0;
            for (int i = 0; i < IterationCount; ++i)
            {
                for (int j = 0; j < data.Length; ++j)
                {
                    var x = data[j];
                    d ^= new DoubleIntUnion(x + 6755399441055744.0).Int;
                }
            }
            return d;
        }
    }
}

更新2016-11-23:

安德烈·阿金欣在Andreynet / coreclr repo上贴了一个question之后,被添加到了1.2.0的里程碑.所以似乎这个问题只是一个监督,而且会被修改.

解决方法

我们来看看(int)Math.Round(data [j])的asm.

LegacyJIT-86:

01172EB0  fld         qword ptr [eax+edi*8+8]  
01172EB4  fistp       dword ptr [ebp-14h]

RyuJIT-64:

`d7350617 c4e17b1044d010  vmovsd  xmm0,qword ptr [rax+rdx*8+10h]
`d735061e e83dce605f      call    clr!COMDouble::Round (`3695d460)
`d7350623 c4e17b2ce8      vcvttsd2si ebp,xmm0

来源clr!COMDouble :: Round:

clr!COMDouble::Round:
`3695d460 4883ec58        sub     rsp,58h
`3695d464 0f29742440      movaps  xmmword ptr [rsp+40h],xmm6
`3695d469 0f57c9          xorps   xmm1,xmm1
`3695d46c f2480f2cc0      cvttsd2si rax,xmm0
`3695d471 0f297c2430      movaps  xmmword ptr [rsp+30h],xmm7
`3695d476 0f28f0          movaps  xmm6,xmm0
`3695d479 440f29442420    movaps  xmmword ptr [rsp+20h],xmm8
`3695d47f f2480f2ac8      cvtsi2sd xmm1,rax
`3695d484 660f2ec1        ucomisd xmm0,xmm1
`3695d488 7a17            jp      clr!COMDouble::Round+0x41 (`3695d4a1)
`3695d48a 7515            jne     clr!COMDouble::Round+0x41 (`3695d4a1)
`3695d48c 0f28742440      movaps  xmm6,xmmword ptr [rsp+40h]
`3695d491 0f287c2430      movaps  xmm7,xmmword ptr [rsp+30h]
`3695d496 440f28442420    movaps  xmm8,xmmword ptr [rsp+20h]
`3695d49c 4883c458        add     rsp,58h
`3695d4a0 c3              ret
`3695d4a1 440f28c0        movaps  xmm8,xmm0
`3695d4a5 f2440f5805c23a7100 
            addsd xmm8,mmword ptr [clr!_real (`37070f70)] ds:`37070f70=3fe0000000000000
`3695d4ae 410f28c0        movaps  xmm0,xmm8
`3695d4b2 e821000000      call    clr!floor (`3695d4d8)
`3695d4b7 66410f2ec0      ucomisd xmm0,xmm8
`3695d4bc 0f28f8          movaps  xmm7,xmm0
`3695d4bf 7a06            jp      clr!COMDouble::Round+0x67 (`3695d4c7)
`3695d4c1 0f8465af3c00    je      clr! ?? ::FNOdobFM::`string'+0xdd8c4 (`36d2842c)
`3695d4c7 0f28ce          movaps  xmm1,xmm6
`3695d4ca 0f28c7          movaps  xmm0,xmm7
`3695d4cd ff1505067000    call    qword ptr [clr!_imp__copysign (`3705dad8)]
`3695d4d3 ebb7            jmp     clr!COMDouble::Round+0x2c (`3695d48c)

如您所见,LegacyJIT-x86使用了非常快的fldfistp对;根据Instruction tables by Agner Fog,Haswell有以下数字:

Instruction | Latency | Reciprocal throughput
------------|---------|----------------------
FLD m32/64  | 3       | 0.5
FIST(P) m   | 7       | 1

RyuJIT-x64直接调用clr!COMDouble :: Round(LegacyJIT-x64做同样的).您可以在dotnet/coreclr回购中找到此方法的源代码.如果您正在使用版本1.0.0,则需要floatnative.cpp:

#if defined(_TARGET_X86_)
__declspec(naked)
double __fastcall COMDouble::Round(double d)
{
    LIMITED_METHOD_CONTRACT;

    __asm {
        fld QWORD PTR [ESP+4]
        frndint
        ret 8
    }
}

#else // !defined(_TARGET_X86_)
FCIMPL1_V(double,COMDouble::Round,double d) 
    FCALL_CONTRACT;

    double tempVal;
    double flrTempVal;
    // If the number has no fractional part do nothing
    // This shortcut is necessary to workaround precision loss in borderline cases on some platforms
    if ( d == (double)(__int64)d )
        return d;
    tempVal = (d+0.5);
    //We had a number that was equally close to 2 integers. 
    //We need to return the even one.
    flrTempVal = floor(tempVal);
    if (flrTempVal==tempVal) {
        if (0 != fmod(tempVal,2.0)) {
            flrTempVal -= 1.0;
        }
    }
    flrTempVal = _copysign(flrTempVal,d);
    return flrTempVal;
FCIMPLEND
#endif // defined(_TARGET_X86_)

如果您正在使用主分支,则可以在floatdouble.cpp中找到类似的代码.

FCIMPL1_V(double,double x)
    FCALL_CONTRACT;

    // If the number has no fractional part do nothing
    // This shortcut is necessary to workaround precision loss in borderline cases on some platforms
    if (x == (double)((INT64)x)) {
        return x;
    }

    // We had a number that was equally close to 2 integers.
    // We need to return the even one.

    double tempVal = (x + 0.5);
    double flrTempVal = floor(tempVal);

    if ((flrTempVal == tempVal) && (fmod(tempVal,2.0) != 0)) {
        flrTempVal -= 1.0;
    }

    return _copysign(flrTempVal,x);
FCIMPLEND

似乎完整的.NET Framework使用相同的逻辑.

因此,(int)Math.Round在x86上比在x64上运行得更快,因为不同JIT编译器的内部实现有所不同.请注意,以后可以更改此行为.

顺便说一下,您可以在BenchmarkDotNet的帮助下编写一个小型可靠的基准测试:

[LegacyJitX86Job,LegacyJitX64Job,RyuJitX64Job]
public class MathRoundBenchmarks
{
    private const int N = 100;
    private double[] data;

    [Setup]
    public void Setup()
    {
        var rand = new Random(0);
        data = new double[N];
        for (int i = 0; i < data.Length; ++i)
        {
            data[i] = rand.NextDouble() * int.MaxValue * 2 +
                      int.MinValue + rand.NextDouble();
        }
    }

    [Benchmark(OperationsPerInvoke = N)]
    public int MathRound()
    {
        int d = 0;
        for (int i = 0; i < data.Length; ++i)
            d ^= (int) Math.Round(data[i]);
        return d;
    }
}

结果:

BenchmarkDotNet.Core=v0.9.9.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-4702MQ cpu 2.20GHz,ProcessorCount=8
Frequency=2143475 ticks,Resolution=466.5321 ns,Timer=TSC
CLR=MS.NET 4.0.30319.42000,Arch=64-bit RELEASE [RyuJIT]
GC=Concurrent Workstation
JitModules=clrjit-v4.6.1586.0

Type=MathRoundBenchmarks  Mode=Throughput

    Method | Platform |       Jit |     Median |    StdDev |
---------- |--------- |---------- |----------- |---------- |
 MathRound |      X64 | LegacyJit | 12.8640 ns | 0.2796 ns |
 MathRound |      X64 |    RyuJit | 13.4390 ns | 0.4365 ns |
 MathRound |      X86 | LegacyJit |  1.0278 ns | 0.0373 ns |

c# – 在x64平台上Math.Round的性能显着下降的更多相关文章

  1. canvas中普通动效与粒子动效的实现代码示例

    canvas用于在网页上绘制图像、动画,可以将其理解为画布,在这个画布上构建想要的效果。本文详细的介绍了粒子特效,和普通动效进行对比,非常具有实用价值,需要的朋友可以参考下

  2. H5混合开发app如何升级的方法

    本篇文章主要介绍了H5混合开发app如何升级的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  3. canvas学习和滤镜实现代码

    这篇文章主要介绍了canvas学习和滤镜实现代码,利用 canvas,前端人员可以很轻松地、进行图像处理,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  4. localStorage的过期时间设置的方法详解

    这篇文章主要介绍了localStorage的过期时间设置的方法详解的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  5. 详解HTML5 data-* 自定义属性

    这篇文章主要介绍了详解HTML5 data-* 自定义属性的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  6. HTML5的postMessage的使用手册

    HTML5提出了一个新的用来跨域传值的方法,即postMessage,这篇文章主要介绍了HTML5的postMessage的使用手册的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. 教你使用Canvas处理图片的方法

    本篇文章主要介绍了教你使用Canvas处理图片的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  8. ios – Swift语言:如何调用SecRandomCopyBytes

    从Objective-C,我可以这样做:在Swift中尝试这个时,我有以下内容:但我得到这个编译器错误:data.mutableBytes参数被拒绝,因为类型不匹配,但我无法弄清楚如何强制参数.解决方法这似乎有效:

  9. 使用Firebase iOS Swift将特定设备的通知推送到特定设备

    我非常感谢PushNotifications的帮助.我的应用聊天,用户可以直接向对方发送短信.但是如果没有PushNotifications,它就没有多大意义.它全部设置在Firebase上.如何将推送通知从特定设备发送到特定设备?

  10. ios – NSData to Data swift 3

    如何将此代码转换为使用Swift3数据?

随机推荐

  1. c# – (wpf)Application.Current.Resources vs FindResource

    所以,我正在使用C#中的WPF创建一个GUI.它看起来像这样:它现在还没有完成.这两行是我尝试制作一种数据表,它们在XAML中是硬编码的.现在,我正在C#中实现添加新的水果按钮功能.我在XAML中有以下样式来控制行的背景图像应该是什么样子:因此,在代码中,我为每列col0,col1和col2创建一个图像,如果我使用以下代码,它添加了一个如下所示的新行:如你所见,它不太正确……为什么一个似乎忽略了一些属性而另一个没有?

  2. c# – 绑定DataGridTemplateColumn

    似乎我已经打了个墙,试图在DataGrid上使用DataTemplates.我想要做的是使用一个模板来显示每个单元格的两行文本.但是似乎无法以任何方式绑定列.以下代码希望显示我想做的事情.注意每个列的绑定:模板列没有这样的东西,因此,这个xaml不可能工作.我注定要将整个DataTemplate复制到每个列,只是对每个副本都有不同的约束?解决方法我不完全确定你想要做什么,但如果您需要获取整行的DataContext,可以使用RelativeSource绑定来移动视觉树.像这样:

  3. c# – 学习设计模式的资源

    最近我来到了这个设计模式的概念,并对此感到非常热情.你能建议一些帮助我深入设计模式的资源吗?

  4. c# – 是否有支持嵌入HTML页面的跨操作系统GUI框架?

    我想开发一个桌面应用程序来使用跨系统,是否有一个GUI框架,允许我为所有3个平台编写一次代码,并具有完全可脚本化的嵌入式Web组件?我需要它有一个API来在应用程序和网页之间进行交流.我知道C#,JavaScript和一些python.解决方法Qt有这样的事情QWebView.

  5. c# – 通过字符串在对象图中查找属性

    我试图使用任意字符串访问嵌套类结构的各个部分.给出以下(设计的)类:我想要从Person对象的一个实例的“PersonsAddress.HousePhone.Number”获取对象.目前我正在使用反思来做一些简单的递归查找,但是我希望有一些忍者有更好的想法.作为参考,这里是我开发的(crappy)方法:解决方法您可以简单地使用标准的.NETDataBinder.EvalMethod,像这样:

  6. c# – 文件下载后更新页面

    FamilyID=0a391abd-25c1-4fc0-919f-b21f31ab88b7&displaylang=en&pf=true它呈现该页面,然后使用以下元刷新标签来实际向用户提供要下载的文件:你可能需要在你的应用程序中做类似的事情.但是,如果您真的有兴趣在文件完全下载后执行某些操作,那么您的运气不佳,因为没有任何事件可以与浏览器进行通信.执行此操作的唯一方法是上传附件时使用的AJAXupload.

  7. c# – 如何在每个机器应用程序中实现单个实例?

    我必须限制我的.net4WPF应用程序,以便每台机器只能运行一次.请注意,我说每个机器,而不是每个会话.我使用一个简单的互斥体实现单实例应用程序,直到现在,但不幸的是,这样一个互斥是每个会话.有没有办法创建机器互连,还是有其他解决方案来实现每个机器应用程序的单个实例?

  8. c# – WCF和多个主机头

    我的雇主网站有多个主机名,都是同一个服务器,我们只是显示不同的皮肤来进行品牌宣传.不幸的是,在这种情况下,WCF似乎不能很好地工作.我试过overridingthedefaulthostwithacustomhostfactory.这不是一个可以接受的解决方案,因为它需要从所有主机工作,而不仅仅是1.我也看过thisblogpost,但是我无法让它工作,或者不是为了解决我的问题.我得到的错误是“这

  9. c# – ASP.NET MVC模型绑定与表单元素名称中的虚线

    我一直在搜索互联网,试图找到一种方式来容纳我的表单元素的破折号到ASP.NET的控制器在MVC2,3或甚至4中的默认模型绑定行为.作为一名前端开发人员,我更喜欢在我的CSS中使用camelCase或下划线进行破折号.在我的标记中,我想要做的是这样的:在控制器中,我会传入一个C#对象,看起来像这样:有没有办法通过一些正则表达式或其他行为来扩展Controller类来适应这种情况?我讨厌这样的事实,我必须这样做:甚至这个:思考?

  10. c# – 用户界面设计工具

    我正在寻找一个用户界面设计工具来显示文档中可能的GUI.我不能生成代码.我知道MicrosoftVisio提供了一个功能.但有什么办法吗?您使用哪种软件可视化GUI?

返回
顶部