所以,我们都知道C {C ++签名/未签名的比较规则-1 > 2u == true
,我有一个情况,我想实现'纠正'有效地比较。
我的问题是,考虑到人们熟悉的众多架构,这会更有效率。显然英特尔和ARM的权重更高。
假设:
int x;
unsigned int y;
if (x < y) {}
推广是否更好:
x < y => (int64)x < (int64)y
或者进行2次比较是否更好,即:
x < y => (x < 0 || x < y)
前者意味着零扩展,符号扩展和一个比较+分支,后者不需要符号扩展操作,而是2个连续的cmp +分支。
传统智慧认为分支比符号扩展更昂贵,这将是管道,但在第一种情况下扩展和单一比较之间存在停滞,而在第二种情况下,我可以想象某些架构可能会对这两种比较进行管道传输。 ,然后是2个条件分支?
存在另一种情况,其中无符号值是比有符号类型小的类型,这意味着可以使用单个零扩展到签名类型的长度,然后进行单个比较...在这种情况下,是否最好使用extend + cmp版本,还是2比较方法仍然是首选?
英特尔?臂?其他? 我不确定这里是否有正确答案,但我想听听人们的意见。如今,低水平的性能很难预测,特别是在英特尔和ARM上越来越多。
编辑:
我应该补充一点,有一个明显的解决方案,其中类型的大小等于架构int宽度;在这种情况下,显然2比较解决方案是优选的,因为促销本身不能有效地执行。显然,我的int
示例符合32位体系结构的这一条件,您可以将思维实验转换为short
,以适用于32位平台的练习。
编辑2:
抱歉,我忘记了u
中的-1 > 2u
! &GT; _&LT;
编辑3:
我想修改这种情况,假设比较的结果是一个实际的分支,结果不是作为布尔值返回的。这就是我喜欢结构外观的方式;虽然这确实提出了一个有趣的观点,即当结果是布尔与分支时,存在另一组排列。
int g;
void fun(int x, unsigned in y) { if((long long)x < (long long)y) g = 10; }
void gun(int x, unsigned in y) { if(x < 0 || x < y) g = 10; }
当您遇到if
;)
答案 0 :(得分:11)
嗯,你已经正确地描述了这种情况:C / C ++无法与单个比较进行完全符号的int / unsigned int比较。
如果对int64的升级比做两次比较要快,我会感到惊讶。根据我的经验,编译器非常善于意识到像这样的子表达式是纯粹的(没有副作用),因此不需要第二个分支。 (您也可以使用按位或(x < 0) | (x < y)
明确选择退出短路。)相反,我的经验是编译器倾向于不对大于本机字大小的整数进行太多特殊情况优化,所以(int64)x < (int64)y
很可能实际上进行完整的int比较。
最重要的是,没有任何咒语可以保证在任何处理器上产生最好的机器代码,但对于最常见的处理器上最常见的编译器,我猜两种比较形式将是并不比促销到int64的形式慢。
编辑:关于Godbolt的一些信息证实,在ARM32上,GCC为int64方法提供了太多机制。 VC在x86上也是如此。但是,对于x64,int64方法实际上是一条指令更短(因为促销和64位比较是微不足道的)。然而,流水线操作可能会使实际表现成为两种方式。 https://godbolt.org/g/wyG4yC答案 1 :(得分:6)
你必须根据具体情况来判断。签名类型将在程序中使用的原因有几个:
int
而不需要太多考虑。0
等整数常量(类型为int
)来偶然结束它们。在1)的情况下,算术应该用带符号算术进行。然后,您应该转换为包含最大期望值所需的最小可能类型。
假设某个值的范围可以从-10000
到10000
。然后,您需要使用16位带符号类型来表示它。然后,平台独立使用的正确类型是int_fast16_t
。
int_fastn_t
和uint_fastn_t
类型要求类型至少与 n 一样大,但如果编译器提供更快的代码,则允许编译器选择更大的类型/更好的对齐。
2)通过研究stdint.h
和停止懒惰来治愈。作为程序员,总是需要考虑程序中声明的每个变量的大小和签名。这必须在声明时完成。或者,如果您稍后获得某种启示,请返回并更改类型。
如果你不仔细考虑这些类型,你绝对肯定会写出许多,通常是微妙的错误。这在C ++中尤其重要,C ++对类型的正确性比C更为挑剔。
&#34;马虎打字&#34;如果使用,实际的预期类型通常是无符号的而不是签名的。考虑这个草率的打字示例:
for(int i=0; i<n; i++)
在这里使用signed int完全没有意义,所以你为什么这么做?您很可能正在遍历数组或容器,然后使用的正确类型是size_t
。
或者,如果你知道n
可以容纳的最大尺寸,例如100,那么你可以使用最合适的类型:
for(uint_fast8_t i=0; i<100; i++)
3)也通过研究治愈。值得注意的是,这些语言中存在隐式促销的各种规则,例如通常的算术转换和整数提升。
答案 2 :(得分:6)
鉴于您提供的具体设置:
int x;
unsigned int y;
并且您明确意图评估x
的值是否在数值上小于y
的值,尊重x
的符号,我倾向于将其写为< / p>
if ((x < 0) || (x < y)) {}
也就是你的第二种选择。它清楚地表达了意图,只要y
类型的最大可表示值至少与x
类型的最大可表示值一样大,它就可以扩展为更宽的类型。因此,如果你愿意规定参数将具有那种形式,那么你甚至可以把它写成 - 避免你的眼睛,C ++的追随者 - 一个宏。
将两个参数转换为带符号的64位整数类型不是可移植的解决方案,因为无法保证这实际上是来自int
或{的促销 {1}}。对于更广泛的类型,它也是不可扩展的。
至于你的两个选择的相对表现,我怀疑有很多不同,但如果它对你很重要,那么你会想要写一个仔细的基准。我可以想象便携式替代方案需要比另一方更多的机器指令,我也可以想象它需要少一个。只有当这样的比较主导了你的应用程序的性能时,单个指令才能以某种方式产生明显的差异。
当然,这是针对您提出的情况而定的。如果你想按照任何顺序处理混合的有符号/无符号比较,对于许多不同的类型,在编译时整理出来,那么基于模板的包装器可以帮助你(这会使得使用宏的问题没有问题),但是我带你去具体询问比较本身的细节。
答案 3 :(得分:5)
两个分支版本肯定会慢一些,但实际上没有一个是x86上的两个分支......也不是单个分支...
例如x86 gcc 7.1将用于C ++源代码:
bool compare(int x, unsigned int y) {
return (x < y); // "wrong" (will emit warning)
}
bool compare2(int x, unsigned int y) {
return (x < 0 || static_cast<unsigned int>(x) < y);
}
bool compare3(int x, unsigned int y) {
return static_cast<long long>(x) < static_cast<long long>(y);
}
制作此程序集(godbolt live demo):
compare(int, unsigned int):
cmp edi, esi
setb al
ret
compare2(int, unsigned int):
mov edx, edi
shr edx, 31
cmp edi, esi
setb al
or eax, edx
ret
compare3(int, unsigned int):
movsx rdi, edi
mov esi, esi
cmp rdi, rsi
setl al
ret
如果您尝试在更复杂的代码中使用这些代码,它们将在99%的情况下被内联。没有描述它只是猜测,但“通过肠道”我会compare3
作为“更快”,特别是在一些代码内执行失序(有点有趣的是,正确的32-> 64推广甚至对于uint参数,虽然它需要相当多的努力才能产生代码调用,与esi
的上部32b中的一些混乱进行比较......但是当它在更复杂的计算中内联时,它可能会摆脱它请注意,参数也已经uint64扩展,因此compare3
更简单+更短。)
...正如我在评论中所说的那样,我没有点击我需要它的任务,例如我无法想象在有效的数据范围未知的地方工作,所以对于我工作的任务C / C ++非常合适,我完全理解它的工作方式(对于有符号和无符号类型的<
已经很好地定义并导致最短/最快的代码,并且发出警告以使我作为程序员负责验证它,并在需要适当更改源的情况下)。
答案 4 :(得分:4)
您可以做的一个便携式技巧是检查是否可以从intmax_t
扩展<stdint.h>
的两个参数,这是实现支持的最广泛的整数类型。您可以检查(sizeof(intmax_t) > sizeof(x) && sizeof(intmax_t) >= sizeof(y))
,如果是,请执行扩展转换。这适用于int
为32位宽且long long int
为64位宽的非常常见的情况。
在C ++中,你可以做一些聪明的事情,你有一个安全比较模板,可以检查std::numeric_limits<T>
的参数。这是一个版本。 (在gcc或clang上编译-Wno-sign-compare
!)
#include <cassert>
#include <cstdint>
#include <limits>
using std::intmax_t;
using std::uintmax_t;
template<typename T, typename U>
inline bool safe_gt( T x, U y ) {
constexpr auto tinfo = std::numeric_limits<T>();
constexpr auto uinfo = std::numeric_limits<U>();
constexpr auto maxinfo = std::numeric_limits<intmax_t>();
static_assert(tinfo.is_integer, "");
static_assert(uinfo.is_integer, "");
if ( tinfo.is_signed == uinfo.is_signed )
return x > y;
else if ( maxinfo.max() >= tinfo.max() &&
maxinfo.max() >= uinfo.max() )
return static_cast<intmax_t>(x) > static_cast<intmax_t>(y);
else if (tinfo.is_signed) // x is signed, y unsigned.
return x > 0 && x > y;
else // y is signed, x unsigned.
return y < 0 || x > y;
}
int main()
{
assert(-2 > 1U);
assert(!safe_gt(-2, 1U));
assert(safe_gt(1U, -2));
assert(safe_gt(1UL, -2L));
assert(safe_gt(1ULL, -2LL));
assert(safe_gt(1ULL, -2));
}
可以通过改变两行来了解浮点数。
答案 5 :(得分:0)
使用一个小模板jiggery-pokery我认为我们可以自动在所有场景中获得最佳结果:
#include<iostream>
#include<cassert>
template<class T> auto make_unsigned(T i) -> T { return i; }
auto make_unsigned(int i) -> unsigned int {
assert(i >= 0);
return static_cast<unsigned int>(i);
}
auto make_unsigned(short i) -> unsigned short {
assert(i >= 0);
return static_cast<unsigned short>(i);
}
auto make_unsigned(long long i) -> unsigned long long {
assert(i >= 0);
return static_cast<unsigned long long>(i);
}
template<
class I1,
class I2,
std::enable_if_t<(std::is_signed<I1>::value and std::is_signed<I2>::value)
or (not std::is_signed<I1>::value and not std::is_signed<I2>::value)>* = nullptr
>
bool unsigned_less(I1 i1, I2 i2) {
return i1 < i2;
};
template<
class I1,
class I2,
std::enable_if_t<std::is_signed<I1>::value and not std::is_signed<I2>::value>* = nullptr
>
bool unsigned_less(I1 i1, I2 i2) {
return (i1 < 0) or make_unsigned(i1) < i2;
};
template<
class I1,
class I2,
std::enable_if_t<not std::is_signed<I1>::value and std::is_signed<I2>::value>* = nullptr
>
bool unsigned_less(I1 i1, I2 i2) {
return not (i2 < 0) and i1 < make_unsigned(i2);
};
int main() {
short a = 1;
unsigned int b = 2;
std::cout << unsigned_less(a, b) << std::endl;
using uint = unsigned int;
using ushort = unsigned short;
std::cout << unsigned_less(ushort(1), int(3)) << std::endl;
std::cout << unsigned_less(int(-1), uint(0)) << std::endl;
std::cout << unsigned_less(int(1), uint(0)) << std::endl;
return 0;
}
答案 6 :(得分:0)
最近在柏林举行的自我设计研讨会上,看看Andrei Alexandrescus的主题演讲。
在其中,他展示了如何在DESIGN时设计一个检查过的int类,他提出的一个功能就是这个 - 如何比较有符号和无符号。
基本上你需要进行2次比较
如果(signed_var&lt; 0)则返回unsigned_var 否则将signed_var提升/转换为unsigned_var,然后比较