草庐IT

c++ - 不违反标准的近恒定时间旋转

coder 2023-05-03 原文

我花了很长时间试图提出一个不违反 C/C++ 标准的恒定时间轮换。

问题是边缘/角落的情况,其中操作在算法中被调用并且这些算法无法更改。例如,以下来自 Crypto++并在 GCC ubsan 下执行测试工具(即,g++ fsanitize=undefined):

$ ./cryptest.exe v | grep runtime
misc.h:637:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:643:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:625:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:637:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:643:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
misc.h:637:22: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'

misc.h:637处的代码:

template <class T> inline T rotlMod(T x, unsigned int y)
{
    y %= sizeof(T)*8;
    return T((x<<y) | (x>>(sizeof(T)*8-y)));
}

Intel 的 ICC 特别无情,它把整个函数调用都去掉了,没有 y %= sizeof(T)*8。几年前我们修复了这个问题,但由于缺乏恒定的时间解决方案,我们保留了其他勘误表。

还有一个痛点。当 y = 0 时,我得到一个条件 32 - y = 32,它设置了未定义的行为。 如果我添加了对if(y == 0) ...的检查,那么代码不满足恒定时间要求。

我研究了许多其他实现,从 Linux 内核到其他加密库。它们都包含相同的未定义行为,因此它似乎是一个死胡同。

如何用最少的指令在几乎恒定的时间内执行旋转?

编辑:接近恒定时间,我的意思是避免分支,因此总是执行相同的指令。我不担心 CPU 微码计时。虽然分支预测在 x86/x64 上可能非常出色,但在嵌入式等其他平台上可能表现不佳。


如果 GCC 则不需要这些技巧或 Clang提供了执行 rotate in near constant time 的内在函数.我什至会满足于“执行旋转”,因为他们甚至没有。

最佳答案

我已链接到此答案以获取其他几个“轮换”问题的完整详细信息,包括 this community wiki question ,应与最佳实践保持同步。

我找到了一篇关于这个问题的博文,看起来它终于解决了(使用足够新的编译器版本)。

John Regehr at the University of Utah推荐他尝试制作旋转功能的版本“c”。我用按位 AND 替换了他的断言,发现它仍然编译为单个旋转 insn。

typedef uint32_t rotwidth_t;  // parameterize for comparing compiler output with various sizes

rotwidth_t rotl (rotwidth_t x, unsigned int n)
{
  const unsigned int mask = (CHAR_BIT*sizeof(x)-1);  // e.g. 31

  assert ( (n<=mask)  &&"rotate by type width or more");
  n &= mask;  // avoid undef behaviour with NDEBUG.  0 overhead for most types / compilers
  return (x<<n) | (x>>( (-n)&mask ));
}

rotwidth_t rot_const(rotwidth_t x)
{
  return rotl(x, 7);
}

这可以在 x 的类型上进行模板化,但在实际使用中可能更有意义,在函数名称中包含宽度(如 rotl32)。通常当你旋转时,你知道你想要什么宽度,这比你当前存储值的大小变量更重要。

还要确保仅将其与无符号类型一起使用。有符号类型的右移会进行算术移位,即按符号位移位。 (这在技术上是依赖于实现的行为,但现在一切都使用 2 的补码。)

Pabigot 在我之前独立提出了相同的想法,and posted it at gibhub .他的版本有 C++ static_assert 检查,以使其在类型范围之外使用旋转计数成为编译时错误。

tested mine with gcc.godbolt.org ,定义了 NDEBUG,用于变量和编译时常量循环计数:

  • gcc:gcc >= 4.9.0、非分支 neg+shifts+或更早版本的最佳代码。
    (编译时常量计数:gcc 4.4.7 很好)
  • clang:clang >= 3.5.0、非分支 neg+shifts+或更早的最佳代码。
    (编译时 const rotate count:clang 3.0 很好)
  • icc 13:最优代码。
    (使用 -march=native 的编译时常量计数:生成较慢的 shld $7, %edi, %edi。没有 -march=native 也可以)

即使是较新的编译器版本也可以处理来自维基百科的常用代码(包含在 Godbolt 示例中),而无需生成分支或 cmov。 John Regehr 的版本具有在旋转计数为 0 时避免未定义行为的优势。

对于 8 位和 16 位旋转有一些注意事项,但是当 nuint32_t 时,编译器对于 32 位或 64 位似乎没问题。请参阅 godbolt link 上的代码中的注释对于我测试各种宽度的 uint*_t 的一些笔记。希望这个成语能被所有编译器更好地识别,以便在未来有更多的类型宽度组合。有时 gcc 会在旋转计数上无用地发出 AND insn,即使 x86 ISA 使用该精确 AND 作为第一步定义了旋转 insn。

“最佳”意味着高效:

# gcc 4.9.2 rotl(unsigned int, unsigned int):
    movl    %edi, %eax
    movl    %esi, %ecx
    roll    %cl, %eax
    ret
# rot_const(unsigned int):
    movl    %edi, %eax
    roll    $7, %eax
    ret

当内联时,编译器首先应该能够将值安排在正确的寄存器中,从而只进行一次循环。

使用较旧的编译器,当旋转计数是编译时常量时,您仍然可以获得理想的代码。 Godbolt 允许您以 ARM 作为目标进行测试,并且它也使用了旋转。在较旧的编译器上使用变量计数,您会得到一些代码膨胀,但没有分支或主要的性能问题,所以这个习惯用法通常应该是安全的。

顺便说一句,我修改了 John Regehr 的原件以使用 CHAR_BIT*sizeof(x),并且 gcc/clang/icc 也为 uint64_t 发出最佳代码。但是,我确实注意到将 x 更改为 uint64_t 而函数返回类型仍然是 uint32_t 会使 gcc 将其编译为移位/或。因此,如果要旋转 64b 的低 32b,请小心将结果转换为单独的序列点中的 32 位。即将结果分配给 64 位变量,然后转换/返回它。 icc 仍然会生成一个 rotate insn,但 gcc 和 clang 不会,因为

// generates slow code: cast separately.
uint32_t r = (uint32_t)( (x<<n) | (x>>( -n&(CHAR_BIT*sizeof(x)-1) )) );

如果有人可以使用 MSVC 对此进行测试,那么了解那里发生的情况会很有用。

关于c++ - 不违反标准的近恒定时间旋转,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31387778/

有关c++ - 不违反标准的近恒定时间旋转的更多相关文章

  1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  2. ruby-on-rails - Ruby 检查日期时间是否为 iso8601 并保存 - 2

    我需要检查DateTime是否采用有效的ISO8601格式。喜欢:#iso8601?我检查了ruby​​是否有特定方法,但没有找到。目前我正在使用date.iso8601==date来检查这个。有什么好的方法吗?编辑解释我的环境,并改变问题的范围。因此,我的项目将使用jsapiFullCalendar,这就是我需要iso8601字符串格式的原因。我想知道更好或正确的方法是什么,以正确的格式将日期保存在数据库中,或者让ActiveRecord完成它们的工作并在我需要时间信息时对其进行操作。 最佳答案 我不太明白你的问题。我假设您想检查

  3. ruby-on-rails - 将 Ruby 中的日期/时间格式化为 YYYY-MM-DD HH :MM:SS - 2

    这个问题在这里已经有了答案:Railsformattingdate(4个答案)关闭4年前。我想格式化Time.Now函数以显示YYYY-MM-DDHH:MM:SS而不是:“2018-03-0909:47:19+0000”该函数需要放在时间中.现在功能。require‘roo’require‘roo-xls’require‘byebug’file_name=ARGV.first||“Template.xlsx”excel_file=Roo::Spreadsheet.open(“./#{file_name}“,extension::xlsx)xml=Nokogiri::XML::Build

  4. ruby - 查找字符串中的内容类型(数字、日期、时间、字符串等) - 2

    我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s

  5. ruby - 将 spawn() 的标准输出/标准错误重定向到 Ruby 中的字符串 - 2

    我想使用spawn(针对多个并发子进程)在Ruby中执行一个外部进程,并将标准输出或标准错误收集到一个字符串中,其方式类似于使用Python的子进程Popen.communicate()可以完成的操作。我尝试将:out/:err重定向到一个新的StringIO对象,但这会生成一个ArgumentError,并且临时重新定义$stdxxx会混淆子进程的输出。 最佳答案 如果你不喜欢popen,这是我的方法:r,w=IO.pipepid=Process.spawn(command,:out=>w,:err=>[:child,:out])

  6. ruby-on-rails - 标准化文件名的字符串,删除重音和特殊字符 - 2

    我正在尝试找到一种方法来规范化字符串以将其作为文件名传递。到目前为止我有这个:my_string.mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n,'').downcase.gsub(/[^a-z]/,'_')但第一个问题:-字符。我猜这个方法还有更多问题。我不控制名称,名称字符串可以有重音符、空格和特殊字符。我想删除所有这些,用相应的字母('é'=>'e')替换重音符号,并将其余的替换为'_'字符。名字是这样的:“Prélèvements-常规”“健康证”...我希望它们像一个没有空格/特殊字符的文件名:“prelevements_routin

  7. 旋转矩阵的几何意义 - 2

    点向量坐标矩阵的几何意义介绍旋转矩阵的几何含义之前,先介绍一下点向量坐标矩阵的几何含义点:在一维空间下就是一个标量,如同一条直线上,以任意某一个位置为0点,以一定的尺度间隔为1,2,3...,相反方向为-1,-2,-3...;如此就形成了一维坐标系,这时候任何一个点都可以用一个数值表示,如点p1=5,即即从原点出发沿着x轴正方向移动5个尺度;点p2=-3,负方向移动3个尺度;     在一维坐标系上过原点做垂直于一维坐标系的直线,则形成了二维坐标系,此时描述一个点需要两个数值来表示点p3=(3,2),即从原点出发沿着x轴正方向移动3个尺度,在此基础上沿着y轴正方向移动两个尺度的位置就是点p3。

  8. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  9. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

  10. sql - 查询忽略时间戳日期的时间范围 - 2

    我正在尝试查询我的Rails数据库(Postgres)中的购买表,我想查询时间范围。例如,我想知道在所有日期的下午2点到3点之间进行了多少次购买。此表中有一个created_at列,但我不知道如何在不搜索特定日期的情况下完成此操作。我试过:Purchases.where("created_atBETWEEN?and?",Time.now-1.hour,Time.now)但这最终只会搜索今天与那些时间的日期。 最佳答案 您需要使用PostgreSQL'sdate_part/extractfunction从created_at中提取小时

随机推荐