Duff's device(C循环展开)

本文详细介绍了Duff'sdevice循环展开技术的原理与应用,通过实例展示了如何利用该技术提高程序效率,并提供了通用化的实现方式及实际调用场景,揭示了其在代码优化中的无穷魅力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Duff's device实际上就是用循环展开的方式来提高程序效率的办法,循环展开可以有效利用CPU资源提升效率,当然我们也可以不借助它,可以分两步走,第一步展开,第二步计算剩下部分,但是代码不简洁,通用性也没有这个强。

下面介绍下Duff's device,假定要copy 20 个字节

int count;                        // Set to 20
{
    int n = (count + 7) / 8;      // n is now 3.  (The "while" is going
                                  //              to be run three times.)

    switch (count % 8) {          // The remainder is 4 (20 modulo 8) so
                                  // jump to the case 4

    case 0:                       // [skipped]
             do {                 // [skipped]
                 *to = *from++;   // [skipped]
    case 7:      *to = *from++;   // [skipped]
    case 6:      *to = *from++;   // [skipped]
    case 5:      *to = *from++;   // [skipped]
    case 4:      *to = *from++;   // Start here.  Copy 1 byte  (total 1)
    case 3:      *to = *from++;   // Copy 1 byte (total 2)
    case 2:      *to = *from++;   // Copy 1 byte (total 3)
    case 1:      *to = *from++;   // Copy 1 byte (total 4)
           } while (--n > 0);     // N = 3 Reduce N by 1, then jump up
                                  //       to the "do" if it's still
    }                             //        greater than 0 (and it is)
}

下面给出一个通用的Duff's device,以及对它的调用

#define DUFF_DEVICE_8(aCount, aAction)	\
do {	\
int count_ = (aCount);	\
int times_ = (count_ + 7) >> 3;	\
switch (count_ & 7){	\
case 0: do { aAction;	\
case 7: aAction;	\
case 6: aAction;	\
case 5: aAction;	\
case 4: aAction;	\
case 3: aAction;	\
case 2: aAction;	\
case 1: aAction;	\
} while (--times_ > 0);	\
}	\
} while (0)


在下面的调用代码中看到了Duff's device的无穷魅力。
int CopyToEEPROM(const void* pSource, void* pDest, unsigned len) {

	// Anything to copy?
	if (len > 0) {
		const char* s = (const char*)pSource;
		char* d = (char*)pDest;

		// First, need to copy data to page buffer
		DUFF_DEVICE_8(len, *d++ = *s++);

		// Start EEPROM write cycle
		HAL_EEPROM_WRITE();

		// Check if data was transferred correctly
		s = (const char*)pSource;
		d = (char*)pDest;
		DUFF_DEVICE_8(len, if (*d++ != *s++) return 1; /* Fail */);
	}
	return 0; // Success
}



http://www.drdobbs.com/a-reusable-duff-device/184406208



                
<think>好的,我需要解决用户关于循环展开优化的问题,特别是当循环次数为奇数时的处理技巧。首先,我得回忆一下循环展开的基本概念。循环展开主要是减少循环次数,每次迭代处理多个元素,从而减少循环控制的开销,比如条件判断和分支预测失败的影响。参考中提到的例子,比如每次处理8个元素,这样可以减少循环次数,提高CPU利用率[^1][^2][^3]。 现在问题是当循环次数是奇数时,比如用户想处理N次循环,但N不能被展开的步长整除,这时候怎么办。通常的循环展开假设循环次数是展开步长的倍数,比如步长4,那么循环次数最好是4的倍数。但如果总次数是奇数,比如100次步长3,那么最后一次迭代可能不足3个元素需要处理。这时候需要处理剩余的元素。 用户提到的奇数次循环,可能是指总次数是奇数,或者展开的步长是奇数。这里可能更指总次数不能被步长整除的情况。例如,步长为4,总次数是5次,那么展开一次处理4个,剩下1次需要单独处理。 我需要解释这种情况下如何处理剩余迭代。常见的做法是分两部分:主循环处理大部分能整除的部分,然后用一个清理循环处理剩下的元素。例如,当步长是4时,主循环处理n//4次,剩下的n%4次由清理循环处理。但这样可能会有性能损失,因为清理循环需要额外的判断和循环。或者可以在主循环之后直接展开剩余的部分,用条件判断来处理不同的剩余情况。 另外,编译器在自动进行循环展开时,可能会生成多个版本的处理代码,或者使用模运算来处理剩余元素。但在手动优化时,开发者需要显式处理剩余迭代。例如,手动展开时,可以计算剩余的次数,并用条件语句逐个处理剩下的元素。 还需要考虑如何避免引入额外的分支判断,因为分支会影响性能。比如,使用Duff's device这样的技术,通过switch和循环结合的方式来处理剩余元素,但这可能影响代码可读性。 可能还需要提到具体的示例代码,比如当步长是4,总次数是奇数时,如何处理。例如,先处理前k个完整的块,然后处理剩下的1到3个元素。或者使用展开后的循环体,在最后处理剩余元素时直接复制循环体内的操作,但次数减少。 此外,需要提到编译器的自动循环展开,比如GCC的#pragma unroll或者编译器优化选项,但编译器在处理剩余迭代时可能不如手动优化灵活,尤其是在步长和循环次数不确定的情况下。 总结步骤应该是:1. 确定展开的步长;2. 计算主循环次数和剩余次数;3. 处理主循环部分;4. 处理剩余元素,可能需要条件判断或展开剩余步骤;5. 考虑代码可读性与性能的平衡。同时,要引用相关的参考资料,比如循环展开优化技术如何减少分支预测失败和循环开销[^1]。 可能还需要注意,当剩余次数较少时,直接使用简单的循环处理可能更高效,而不是做复杂的展开,因为额外判断的开销可能抵消了展开带来的收益。因此,需要根据具体情况权衡。 最后,需要生成相关问题,帮助用户进一步理解循环展开的其他方面。</think>### 循环展开中奇数次循环的处理技巧 循环展开通过减少循环迭代次数提升性能,但在循环次数无法被展开步长整除时(例如奇数循环次数),需特殊处理剩余元素。以下是具体实现方法: --- #### **1. 手动处理剩余迭代** 假设原始循环步长为$k$,总次数为$N$: - **主循环**:处理$\lfloor N/k \rfloor$次完整迭代,每次处理$k$个元素。 - **清理循环**:处理剩余$r = N \bmod k$个元素。 **示例代码(步长=4,总次数=7)**: ```c int i = 0; int remainder = N % 4; // 假设N=7,remainder=3 // 主循环处理4个元素/次 for (; i < N - remainder; i += 4) { a[i] = b[i] + c[i]; a[i+1] = b[i+1] + c[i+1]; a[i+2] = b[i+2] + c[i+2]; a[i+3] = b[i+3] + c[i+3]; } // 处理剩余元素 switch (remainder) { case 3: a[i+2] = b[i+2] + c[i+2]; case 2: a[i+1] = b[i+1] + c[i+1]; case 1: a[i] = b[i] + c[i]; } ``` **原理**:通过`switch`穿透特性直接处理剩余元素,避免额外分支判断[^2]。 --- #### **2. 动态计算剩余步长** 通过循环展开结合条件判断,动态调整处理步长: ```c for (int i = 0; i < N; ) { // 每次处理最大可能的步长 int step = (N - i >= 4) ? 4 : (N - i); for (int j = 0; j < step; j++) { a[i+j] = b[i+j] + c[i+j]; } i += step; } ``` **优点**:代码简洁,适用于任意步长和循环次数。 --- #### **3. 编译器优化支持** 现代编译器(如GCC/Clang)可通过编译指令自动处理剩余迭代: ```c #pragma GCC unroll 4 for (int i = 0; i < N; i++) { a[i] = b[i] + c[i]; } ``` **原理**:编译器生成多个版本循环代码,根据剩余迭代选择最优路径[^1]。 --- #### **关键优化原则** 1. **减少分支预测失败**:通过展开降低条件判断频率[^2]。 2. **平衡展开步长**:步长过长可能导致缓存压力,建议选择4-8。 3. **保留代码可读性**:手动展开可能降低可维护性,优先依赖编译器优化。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值