5

前几天我在 Verilog 中学到了一个很酷的技巧。当你需要重复做某事时。您可以使用移位寄存器来计算增量的数量。只需将 1 从 LSB 移到 MSB,当它到达 MSB 时,您就完成了。

在 C 中它会是这样的:

for(j=0b1; !(j & (1<<16)); j=j<<1)
{
/*do a thing 16 times*/
}

我知道由于位宽,它的用途有限,但它不涉及任何添加,因此速度很快。所以我的问题是:这有什么用吗?在 C 或任何其他高级语言中使用是否值得?

也许在资源有限的嵌入式系统中。

谢谢

4

8 回答 8

8

这是非常值得的。它使代码变得不那么清晰和难以阅读,并且性能差异可以忽略不计。

您的编译器可以比您更好地执行这些类型的优化。出于性能原因,甚至可能会展开像这样的短循环。但是,如果您像这样编写循环,编译器可能无法轻松解决这个问题,因此您甚至可能会减慢程序的速度。

这确实是一个微优化的案例,几乎可以肯定它永远不会对您的程序运行时产生显着影响。

于 2012-05-25T19:35:51.770 回答
5

在我看来,大多数评论/回答的人并不真正理解提问者在说什么。Verilog 语言用于硬件设计,硬件设计与软件设计非常不同,没有 CPU 周期或类似的东西。但是,简短的答案仍然是:不。长答案:

当然,移位比加法要简单得多。对于移位,从 FF(触发器)到 FF 的逻辑要少得多。此外,进位必须从 LSB 位传播到 MSB 位,这意味着 log2(N) 级逻辑(N 是计数器将达到的最高值)。另一方面,移位寄存器将使用 N 个 FF,而加法器将仅使用 log2(N) 个 FF。所以有一个性能/面积折衷,这也很大程度上取决于 N。关于加法器的一些“独立”信息: http ://en.wikipedia.org/wiki/Adder_%28electronics%29 找不到类似的移位文章,但是一旦你了解了加法器,移位器应该是显而易见的。

当您在 RTL 中设计状态机时,这可能很重要。但是您提供的代码实际上与上述无关。verilog 中的这个“for”循环意味着所有的“工作”都将在一个循环中完成。所以实际上会有N个逻辑。这个循环与实现无关。它甚至可能只会混淆verilog编译器吐出一些奇怪的东西并影响模拟(CPU周期确实很重要,上面的答案是有效的)。有更多工具经验的人可以对此发表评论。

于 2012-05-26T06:41:29.300 回答
2

(根据 Stefan 的回答,我假设您是在询问受 Verilog 版本启发的 C 版本,而不是在 Verilog 中执行此操作。)

在许多架构上,这实际上更糟,因为位移需要额外的指令,而循环变量的加法是完全免费的。

完全地?

是的。因为在许多架构上,有单个指令会递减计数器并在计数器非零时进行分支——这些指令与任何其他比较和分支指令所花费的时间一样多。然而,如果您正在轮班,则需要一个额外的指令周期。如果您的平台没有“比较相等和分支”指令,那就更糟了——而且不是所有的都有;有些让你在两条指令中减去并比较为零。

即使在没有递减比较分支指令的 RISC 平台上,倒计时循环也可能更快,因为您可以简单地减去(一条指令)并使用分支如果非零指令 - 而在您的循环中,您需要移位(一条指令)和一个按位与(一条指令)在分支如果为零之前。这是假设你甚至有一个零分支。

此外,对于一个简单的for (i = 0; i < N; i++)循环,编译器将其转换为“倒数到 0”循环是微不足道的,如果这样更快的话——你甚至不需要自己做那点聪明的事情。

于 2012-05-26T06:50:56.920 回答
1

增量是加法的一种非常特殊的情况。在大多数处理器中,当然在大多数 RISC 处理器中,移位和增量在执行时间上是相同的。事实上,在大多数架构中,添加也不再需要。

当您保持循环代码惯用时,优化器很可能会简单地展开循环并在任何情况下更快地渲染它。如果您使循环机制“不寻常”,优化器可能无法对其进行优化。

于 2012-05-27T22:15:38.400 回答
1

快点?您确定吗?至少在 MIPS 架构上,位移与加法的时间完全一样。如果最常见的面向消费者的处理器架构也不是这样,我会感到惊讶。

此外,正如 Oleksi 所指出的,这很难阅读。可能不值得不存在的速度增益。

于 2012-05-25T19:39:06.753 回答
1

在真正的 CPU 中,加法是你能做的最快的事情之一。位移并没有更快。而且你会让编译器更难有效地优化。

于 2012-05-25T19:38:47.860 回答
1

它不涉及任何添加,因此速度很快

哪个 CPU 架构的转变比加法快?此外,是什么让您认为该特定架构的编译器不会自动从加法到移位进行优化,如果结果证明移位更快?

这个有什么用吗?

出于优化目的,没有任何用途。

是的,出于其他目的,这样的代码通常用于屏蔽字节的各个位。我相信两种最常见的方法是:

uint8_t mask; 

for(mask = 0x01; mask != 0x00; mask<<=1)
{
  do_something (data & mask);
}

或者

for(i=0; i<8; i++)
{
  do_something (data & (1<<i));
}
于 2012-05-28T11:47:03.607 回答
0

一般来说,如果您希望始终循环特定次数 > 0 并最小化循环开销,那么我认为这将是“最好的”:

unsigned i = 16;

do {
// do something here
} while (--i);



You might get the same result with:

unsigned i = 0x8000;

do {
// do something here
} while (i>>=1);

那时,您将不得不查看程序集。

于 2012-05-26T02:36:57.037 回答