是否有夹实数比使用if语句或三元运营商更有效的方法? 我想无论是双打和32位固定点实现(16.16)做到这一点。 我不要求的代码,可以处理这两种情况; 他们将在不同的功能来处理。
很显然,我可以这样做:
double clampedA;
double a = calculate();
clampedA = a > MY_MAX ? MY_MAX : a;
clampedA = a < MY_MIN ? MY_MIN : a;
要么
double a = calculate();
double clampedA = a;
if(clampedA > MY_MAX)
clampedA = MY_MAX;
else if(clampedA < MY_MIN)
clampedA = MY_MIN;
固定点的版本将使用函数/宏比较。
这是在代码的性能关键部分做了,所以我在寻找有效率的方式做到这一点越好(我怀疑将涉及的位操作)
编辑:它必须是标准/便携式C,特定于平台的功能是任何兴趣不在这里。 此外, MY_MIN
和MY_MAX
是作为值I要夹紧的(在上面的例子中双打)相同的类型。
Answer 1:
对于16.16表示,简单的三元不太可能被做得更好速度明智的。
而对于双打,因为你需要它的标准/便携式C,任何种类的位小提琴演奏会结束得很惨。
即使位小提琴是可能的(我怀疑),你会依靠双打的二进制表示。 这一点(和它们的大小)是依赖于实现。
也许你可以“猜”这个用的sizeof(双),然后比较反对共同二进制表示的各种双值的布局,但我认为你是一个隐藏不了了之。
最好的规则是告诉编译器你想要什么(即三元),并让它优化你。
编辑:赔礼道歉时间。 我只是测试quinmars想法(如下图),和它的作品 - 如果你有IEEE-754浮点数。 这给了下面的代码的约20%的速度提升。 IObviously不可移植的,但我觉得有可能会问你的编译器,如果它使用IEEE754浮点格式与#IF标准的方式...?
double FMIN = 3.13;
double FMAX = 300.44;
double FVAL[10] = {-100, 0.23, 1.24, 3.00, 3.5, 30.5, 50 ,100.22 ,200.22, 30000};
uint64 Lfmin = *(uint64 *)&FMIN;
uint64 Lfmax = *(uint64 *)&FMAX;
DWORD start = GetTickCount();
for (int j=0; j<10000000; ++j)
{
uint64 * pfvalue = (uint64 *)&FVAL[0];
for (int i=0; i<10; ++i)
*pfvalue++ = (*pfvalue < Lfmin) ? Lfmin : (*pfvalue > Lfmax) ? Lfmax : *pfvalue;
}
volatile DWORD hacktime = GetTickCount() - start;
for (int j=0; j<10000000; ++j)
{
double * pfvalue = &FVAL[0];
for (int i=0; i<10; ++i)
*pfvalue++ = (*pfvalue < FMIN) ? FMIN : (*pfvalue > FMAX) ? FMAX : *pfvalue;
}
volatile DWORD normaltime = GetTickCount() - (start + hacktime);
Answer 2:
老问题,但我工作的今天,这一问题(与双打/花车)。
最好的办法是使用SSE MINSS / MAXSS的花车和SSE2 MINSD / MAXSD双打。 这些都是网点,并采取每一个时钟周期,并且易于使用多亏了编译器内在。 他们赋予比用的std ::最小/最大钳位相比,其性能提高了一个数量级以上。
您可能会感到惊奇。 我当然没有! 不幸的是VC ++ 2010使用简单比较的标准::最小/最大即便/弓:SSE2和/ FP:快速启用。 我不能为其他编译器说话。
下面是必要的代码在VC ++做到这一点:
#include <mmintrin.h>
float minss ( float a, float b )
{
// Branchless SSE min.
_mm_store_ss( &a, _mm_min_ss(_mm_set_ss(a),_mm_set_ss(b)) );
return a;
}
float maxss ( float a, float b )
{
// Branchless SSE max.
_mm_store_ss( &a, _mm_max_ss(_mm_set_ss(a),_mm_set_ss(b)) );
return a;
}
float clamp ( float val, float minval, float maxval )
{
// Branchless SSE clamp.
// return minss( maxss(val,minval), maxval );
_mm_store_ss( &val, _mm_min_ss( _mm_max_ss(_mm_set_ss(val),_mm_set_ss(minval)), _mm_set_ss(maxval) ) );
return val;
}
双精度代码是除与xxx_sd代替相同。
编辑:最初我写的钳位功能评价。 但看汇编输出。我注意到,VC ++编译器是不是足够聪明,剔除冗余的举动。 少了一个指令。 :)
Answer 3:
GCC和铛生成以下简单的,直接的,可移植的代码美丽的装配:
double clamp(double d, double min, double max) {
const double t = d < min ? min : d;
return t > max ? max : t;
}
> gcc -O3 -march=native -Wall -Wextra -Wc++-compat -S -fverbose-asm clamp_ternary_operator.c
GCC生成的组件:
maxsd %xmm0, %xmm1 # d, min
movapd %xmm2, %xmm0 # max, max
minsd %xmm1, %xmm0 # min, max
ret
> clang -O3 -march=native -Wall -Wextra -Wc++-compat -S -fverbose-asm clamp_ternary_operator.c
铛生成的组件:
maxsd %xmm0, %xmm1
minsd %xmm1, %xmm2
movaps %xmm2, %xmm0
ret
三条指令(不包括RET),别无分店。 优秀的。
将其用4.7 GCC在Ubuntu 13.04测试和铛3.2与Core i3的中号350在一个侧面说明中,简单的C ++代码调用的std ::分钟和std ::最大产生的相同组件。
这是双打。 而对于INT,GCC和铛生成汇编与五个指令(不包括RET)和没有分支。 也很好。
我目前不使用固定点,所以我不会放弃固定点意见。
Answer 4:
如果您的处理器具有绝对值的快速指令(如x86的一样),你可以做一个网点的最小值和最大值,这将是比快if
语句或三元操作。
min(a,b) = (a + b - abs(a-b)) / 2
max(a,b) = (a + b + abs(a-b)) / 2
如果条件之一是零(因为往往当你夹紧的情况下)的代码,进一步简化了一下:
max(a,0) = (a + abs(a)) / 2
当你结合这两个操作就可以更换两节/2
到单个/4
或*0.25
节省了一步。
下面的代码是3倍以上比三元快上我的速龙II X2,使用FMIN = 0优化时。
double clamp(double value)
{
double temp = value + FMAX - abs(value-FMAX);
#if FMIN == 0
return (temp + abs(temp)) * 0.25;
#else
return (temp + (2.0*FMIN) + abs(temp-(2.0*FMIN))) * 0.25;
#endif
}
Answer 5:
三元运算符是真的要走的路,因为大多数编译器能够编译成一个使用条件移动,而不是分公司的(因而避免了错误预测的处罚和管道气泡等)的本地硬件的操作。 位操作很可能导致负载命中商店 。
具体而言,PPC和x86与SSE2有一个可以表示为一个内在的东西像这样的硬件运算:
double fsel( double a, double b, double c ) {
return a >= 0 ? b : c;
}
其优点是,它这个管道内,而不会造成一个分支。 事实上,如果你的编译器使用的内在,你可以用它来直接实现您的钳:
inline double clamp ( double a, double min, double max )
{
a = fsel( a - min , a, min );
return fsel( a - max, max, a );
}
我强烈建议您不要使用整数运算双打的位操作 。 在大多数现代CPU还有比通过采取往返于dcache中移动其他双和int寄存器之间的数据没有直接的手段。 这将导致数据的危险称为负载命中-Store基本上清空出CPU管线直到存储器中的写入已经完成(通常大约40个循环左右)。
唯一的例外是,如果双精度值已经在内存中,而不是在寄存器中:在这种情况下,没有负载命中店的危险。 但是你的例子表明你刚刚计算出的双和,这意味着它很可能仍处于XMM1函数返回它。
Answer 6:
IEEE 754浮点的位进行排序的方式,如果你比较解释为一个整数位,你会得到相同的结果,如果直接漂浮你对它们进行比较。 所以,如果你发现或者知道一种夹,你可以使用它的整数(IEEE 754)浮也。 对不起,我不知道一个更快的方法。
如果您有存储在阵列中的彩车你可以考虑使用一些CPU的扩展名如SSE3,作为rkj说。 你可以看看liboil它所有肮脏的工作适合你。 保持你的程序移植,如果可能使用了速度更快的CPU指令。 (我不知道寿OS /编译器无关liboil如何)。
Answer 7:
而不是测试和分支,我通常使用这种格式用于夹紧:
clampedA = fmin(fmax(a,MY_MIN),MY_MAX);
虽然我从来没有做过的编译代码的任何性能分析。
Answer 8:
实际上,没有像样的编译器将作出if()语句和之间的区别?:表达式。 该代码是很简单的,他们将能够发现可能的路径。 这就是说,你的两个例子是不相同的。 使用等效代码:?会
a = (a > MAX) ? MAX : ((a < MIN) ? MIN : a);
作为避免A <MIN测试时> MAX。 现在,可以有所作为的,因为编译器,否则将有被发现两次测试之间的关系。
如果夹紧是罕见的,你可以测试需要有一个测试钳:
if (abs(a - (MAX+MIN)/2) > ((MAX-MIN)/2)) ...
例如用MIN = 6和MAX = 10,这将首先由8位移的向下,然后检查它是否处在-2和+2之间。 这是否可以节省任何依赖于分支的相对成本很多。
Answer 9:
这里有类似可能更快地实现@罗迪的回答 :
typedef int64_t i_t;
typedef double f_t;
static inline
i_t i_tmin(i_t x, i_t y) {
return (y + ((x - y) & -(x < y))); // min(x, y)
}
static inline
i_t i_tmax(i_t x, i_t y) {
return (x - ((x - y) & -(x < y))); // max(x, y)
}
f_t clip_f_t(f_t f, f_t fmin, f_t fmax)
{
#ifndef TERNARY
assert(sizeof(i_t) == sizeof(f_t));
//assert(not (fmin < 0 and (f < 0 or is_negative_zero(f))));
//XXX assume IEEE-754 compliant system (lexicographically ordered floats)
//XXX break strict-aliasing rules
const i_t imin = *(i_t*)&fmin;
const i_t imax = *(i_t*)&fmax;
const i_t i = *(i_t*)&f;
const i_t iclipped = i_tmin(imax, i_tmax(i, imin));
#ifndef INT_TERNARY
return *(f_t *)&iclipped;
#else /* INT_TERNARY */
return i < imin ? fmin : (i > imax ? fmax : f);
#endif /* INT_TERNARY */
#else /* TERNARY */
return fmin > f ? fmin : (fmax < f ? fmax : f);
#endif /* TERNARY */
}
见计算两个整数的最小值(MIN)或最高(最大)没有分支和比较浮点数
在IEEE浮点和双精度格式被设计成使得数字“字典顺序”,它 - 在IEEE建筑师威廉Kahan的的话的意思是“如果以相同的格式2浮点数是有序的(比如说X <Y),则他们是有序以同样的方式时,他们的位被重新解释为符号 - 幅度整数“。
测试程序:
/** gcc -std=c99 -fno-strict-aliasing -O2 -lm -Wall *.c -o clip_double && clip_double */
#include <assert.h>
#include <iso646.h> // not, and
#include <math.h> // isnan()
#include <stdbool.h> // bool
#include <stdint.h> // int64_t
#include <stdio.h>
static
bool is_negative_zero(f_t x)
{
return x == 0 and 1/x < 0;
}
static inline
f_t range(f_t low, f_t f, f_t hi)
{
return fmax(low, fmin(f, hi));
}
static const f_t END = 0./0.;
#define TOSTR(f, fmin, fmax, ff) ((f) == (fmin) ? "min" : \
((f) == (fmax) ? "max" : \
(is_negative_zero(ff) ? "-0.": \
((f) == (ff) ? "f" : #f))))
static int test(f_t p[], f_t fmin, f_t fmax, f_t (*fun)(f_t, f_t, f_t))
{
assert(isnan(END));
int failed_count = 0;
for ( ; ; ++p) {
const f_t clipped = fun(*p, fmin, fmax), expected = range(fmin, *p, fmax);
if(clipped != expected and not (isnan(clipped) and isnan(expected))) {
failed_count++;
fprintf(stderr, "error: got: %s, expected: %s\t(min=%g, max=%g, f=%g)\n",
TOSTR(clipped, fmin, fmax, *p),
TOSTR(expected, fmin, fmax, *p), fmin, fmax, *p);
}
if (isnan(*p))
break;
}
return failed_count;
}
int main(void)
{
int failed_count = 0;
f_t arr[] = { -0., -1./0., 0., 1./0., 1., -1., 2,
2.1, -2.1, -0.1, END};
f_t minmax[][2] = { -1, 1, // min, max
0, 2, };
for (int i = 0; i < (sizeof(minmax) / sizeof(*minmax)); ++i)
failed_count += test(arr, minmax[i][0], minmax[i][1], clip_f_t);
return failed_count & 0xFF;
}
在控制台:
$ gcc -std=c99 -fno-strict-aliasing -O2 -lm *.c -o clip_double && ./clip_double
它打印:
error: got: min, expected: -0. (min=-1, max=1, f=0)
error: got: f, expected: min (min=-1, max=1, f=-1.#INF)
error: got: f, expected: min (min=-1, max=1, f=-2.1)
error: got: min, expected: f (min=-1, max=1, f=-0.1)
Answer 10:
我尝试了SSE处理这一自己,并汇编输出看上去颇有几分清洁,所以我起初感到鼓舞,但它的时序上千次后,它实际上是相当慢一点。 它确实看起来像VC ++编译器是不是足够聪明,知道什么是你真正打算,并且它似乎当它不应该的XMM寄存器和存储器之间移动的东西来回。 这就是说,我不知道为什么编译器是不是足够聪明的使用在三元运算符的SSE最小/最大指令时,它似乎使用SSE指令对所有浮点计算反正。 在另一方面,如果你正在编译为PowerPC,您可以使用FSEL的FP寄存器内在的,并且它的方式更快。
Answer 11:
如果我理解正确的,要限制的值“a”到MY_MIN和的my_max之间的范围。 该类型的“a”是一个双。 您没有指定MY_MIN或的my_max的类型。
简单的表达:
clampedA = (a > MY_MAX)? MY_MAX : (a < MY_MIN)? MY_MIN : a;
应该做的伎俩。
我觉得有可能是被做了一个小的优化,如果的my_max和MY_MIN正好是整数:
int b = (int)a;
clampedA = (b > MY_MAX)? (double)MY_MAX : (b < MY_MIN)? (double)MY_MIN : a;
通过改变到整数比较,有可能你可能会得到一个轻微的速度优势。
Answer 12:
如果要使用快速绝对值指令,看看这个文档片断的代码,我在发现小型机 ,它夹一个浮动区间[0,1]
clamped = 0.5*(fabs(x)-fabs(x-1.0f) + 1.0f);
(I简化了码的位)。 我们可以考虑一下为采用两个值,一是体现为> 0
fabs(x)
而另一反射约1.0至是<1.0
1.0-fabs(x-1.0)
我们把他们的平均值。 如果是在范围内,那么这两个值是相同为x,那么他们平均将再次为x。 如果超出范围,则该值之一是x,另一个是x翻转“边界”的观点,所以他们平均会精确的边界点。
Answer 13:
如上所指出的,FMIN / fmax的功能很好地工作(在GCC,与-ffast-数学)。 虽然gfortran具有图案使用对应于最大/最小值,克++不IA指令。 在国际刑事法院必须改用标准::最小/最大,因为ICC不允许短切的规格如何与非有限操作数FMIN / FMAX工作。
Answer 14:
我的2美分C ++。 可能不是任何不同于使用三元运营商,希望不产生分支代码
template <typename T>
inline T clamp(T val, T lo, T hi) {
return std::max(lo, std::min(hi, val));
}
文章来源: Fastest way to clamp a real (fixed/floating point) value?