纳秒到毫秒 - 快速除以1000000

时间:2009-08-13 04:13:07

标签: c++ gcc solaris sparc

我想将输出从gethrtime转换为毫秒。

这样做的显而易见的方法是除以1000000。 但是,我经常这样做,并想知道它是否会成为瓶颈。

处理1000000这样的数字时是否有优化的除法运算?

注意:任何代码都必须是可移植的。我正在使用gcc,这通常是在Sparc硬件上

使用下面的代码进行一些快速测试...希望是对的。

#include <sys/time.h>
#include <iostream>

using namespace std;

const double NANOSECONDS_TO_MILLISECONDS = 1.0 / 1000000.0;

int main()
{
    hrtime_t start;
    hrtime_t tmp;
    hrtime_t fin;

    start = gethrtime();
    tmp = (hrtime_t)(start * NANOSECONDS_TO_MILLISECONDS);
    fin = gethrtime();

    cout << "Method 1"
    cout << "Original val: " << start << endl;
    cout << "Computed: " << tmp << endl;
    cout << "Time:" << fin - start << endl;

    start = gethrtime();
    tmp = (start / 1000000);
    fin = gethrtime();

    cout "Method 2"    
    cout << "Original val: " << start << endl;
    cout << "Computed: " << tmp << endl;
    cout << "Time:" << fin - start << endl;

    return 0;
}  

示例输出:

Original val: 3048161553965997
Computed: 3048161553
Time:82082
Original val: 3048161556359586
Computed: 3048161556
Time:31230

Original val: 3048239663018915
Computed: 3048239663
Time:79381
Original val: 3048239665393873
Computed: 3048239665
Time:31321

Original val: 3048249874282285
Computed: 3048249874
Time:81812
Original val: 3048249876664084
Computed: 3048249876
Time:34830

如果这是正确的,那么在这种情况下,乘以倒数的倍数实际上更慢。这可能是由于使用浮点数学而不是定点数学。我会坚持整数除法,然后几乎不需要任何时间。

10 个答案:

答案 0 :(得分:49)

让你的编译器弄明白!

说真的,如果你真的担心这个级别的优化(除非它出现在配置文件中,否则你不应该这样做),你应该习惯于查看编译器的汇编语言输出。你会惊讶于编译器代表你做的事情。

所有推荐数学技巧的人要么编写错误,要么低估他们的编译器。例如,尝试编译此函数:

unsigned long div1000000(unsigned long n) {
  return n / 1000000UL;
}

在x86(-O3,-fomit-frame-pointer)上用gcc 4.3.3编译,我得到:

$ objdump -d div.o -M intel

test2.o:     file format elf32-i386


Disassembly of section .text:

00000000 <div1000000>:
   0:   b8 83 de 1b 43          mov    eax,0x431bde83
   5:   f7 64 24 04             mul    DWORD PTR [esp+0x4]
   9:   c1 ea 12                shr    edx,0x12
   c:   89 d0                   mov    eax,edx
   e:   c3                      ret    

换句话说,编译器使用n / 1000000UL并将其转换为(unsigned long long)(n * 0x431bde83) >> (0x12 + 32)。为什么这样做?在我的头顶,我不知道!但是编译器认为它比发布原生鸿沟更快。

故事的道德:

  • 除非您确定这是一个瓶颈,否则不要对此进行优化。
  • 不要做花哨的算术(乘以倒数,移位等),除非你已经知道你的编译器正在做什么,你认为你可以击败它。
  • 对结果进行基准测试 - 如果你已经证明你已经超越了你的编译器,那么只留下像花哨的诅咒一样的疣。

答案 1 :(得分:33)

分部是一项昂贵的操作。我非常怀疑1000000除以后的操作是否会接近应用程序的主要瓶颈。浮点处理器比任何类型的“技巧”都快,而不仅仅是单一操作。

答案 2 :(得分:15)

我很惊讶没有人得到这个......

  • 除法与乘法除以
  • 相同
  • 乘以2的分数幂是快的:只是位移
  • 整体划分涉及四舍五入
  • 向下舍入就像乘以稍微小一点(达到某个点,你需要知道你的范围)

所以,

const uint64_t numerator = (1LL<<32)/1000000;

...

millionths = ( number * numerator ) >> 32;

快速!

答案 3 :(得分:3)

乘以1 / 1,000,000。它应该更快。我的谷歌搜索说要加快分歧,乘以倒数。因此,如果有一组相对已知的可能值,我会预先计算倒数或倒数列表,然后相乘。

雅各

答案 4 :(得分:3)

  

但是,我经常这样做,并想知道它是否会成为瓶颈。

首先要做的事情。如果您认为这将成为瓶颈,个人资料有问题的代码并确定无误。

如果,(且仅当)这是你的瓶颈,那么就要努力改进它。

现在,改进你的改进方案:

1.您可能不需要立即转换为毫秒。如果您只是收集数据,只需存储从gethrtime()返回的完整64位数字并完成它。人类需要阅读的任何内容都可以在以后进行后期处理,或者在更新的频率上进行后期处理。

2.如果您正在计算一些重复事件,您可以尝试在两次调用之间执行差异的划分,如果您是非常正在经常调用gethrtime()以产生瓶颈:

static hrtime_t oldtime;
hrtime_t newtime = gethrtime();
int milliseconds = fastDivByOneMillion((UI32)(newtime - oldtime));
oldtime = newtime;

3.您可以将fastDivByOneMillion()实现为乘法,并将幂除以2:

int fastDivByOneMillion(UI32 nanoseconds)
{
    return (int)((UI64)nanoseconds * 4295 >> 32);
}

注意:

  • 您的编译器可以找出在硬件上执行>> 32的最佳方法。大多数情况下,这只是一个或两个时钟周期
  • 我使用UI32UI64来表示32位和64位无符号数。
  • 所有这些都需要更多的分析,以确保它实际上产生了可衡量的改进。

  • 答案 5 :(得分:2)

    作为Joshua Haberman mentioned,您的编译器可能已经将除法乘以常数1000000转换为乘以“幻数”后跟移位(如果除法是整数运算)。您可以在Henry Warren的“Hacker's Delight”一书以及随附网站上了解更多有关正在发生的事情的详细信息:

    他甚至有一个页面,其中包含一个用于幻数的Javascript计算器:

    答案 6 :(得分:2)

    首先,明显的免责声明:除非你每秒执行几百万次至少,否则它不会成为瓶颈,你应该放弃它。过早优化等等。

    其次,您需要多大准确的结果?在二进制和十进制之间转换的一个方便的经验法则是2 ^ 10~ = 10 ^ 3.

    换句话说,百万大致等于2 ^ 20。所以你可以正确地移动20.当然,编译器不会自动为你做这件事,因为它改变了结果。但是如果你愿意以微小的准确度生活,该部门实际上是一个真正的性能问题,这将是我的建议。

    答案 7 :(得分:0)

    可以将整数除法转换为一系列更简单的操作。由Terje Mathisen推广的通用方法概述于第136页 Optimizing subroutines in assembly language。如果您事先知道数据类型的宽度以及您要划分的内容,那么将引导您完成如何将其转换为更简单的操作,理论上这可能比必须处理的更通用的除法操作更快任何除数。如果你担心其中一些整数的大小不同,仍然会有一些平台问题需要关注。

    除非您实际上是用汇编语言对此进行编程,否则我会反对您实际上在改进SPARC除法实现过程中的任何内容。也许如果你使用的是一个非常古老的SPARC V7处理器,从分割前implemented in hardware开始,你可能会得到一些改进,但即使这样,我也会打赌内置分区更快。

    无论如何,我怀疑你已经在这里进行了一些过早的优化。您应该首先分析您已经获得的应用程序,然后假设此部门对其运行时有任何重大影响,您应该同样地对该部门进行任何更改,以证明它按预期工作。你可以很容易地获得你认为会更快执行的代码,但实际上现在并不是这样,因为CPU缓存有些复杂。

    答案 8 :(得分:0)

    如果你可以解决这个问题,这是我的解决方案。

    • 使用整数而不是浮点数(它们 更快)
    • 通过将位向右移位(除了更便宜除以浮标之外的任何东西)除以1048576

    并说服自己毫秒应该是base2而不是base10。 ; - )

    答案 9 :(得分:0)

    1/1000000是0.000000000000000000 0100 0011 0001 1011 1101 1110 1000 0010 1101 0111 1011 0110 0011 01二进制 - 这是0x431BDE82 * 2 ^ -18

    因此n / 1000000相当于(n * 0x431BDE82)&gt;&gt; 18

    此外,n / 1000000相当于(n * 0x8637BD04)&gt;&gt; 19

    请注意,这是一个“定点”计算,你应该知道精度可能会丢失。