(t_Some *a)
和(t_Some *b)
。考虑到时间与空间的权衡,增加临时空间能提高速度吗?例如,(char *tmp)
与(int *tmp)
哪个更好?我正在寻找一个可移植的解决方案。原型:
void swap_elements_of_array(void* base, size_t size_of_element, int a, int b);
#include <stddef.h>
void swap_blocks_with_loop( void* const a, void* const b, const size_t n )
{
unsigned char* p;
unsigned char* q;
unsigned char* const sentry = (unsigned char*)a + n;
for ( p = a, q = b; p < sentry; ++p, ++q ) {
const unsigned char t = *p;
*p = *q;
*q = t;
}
}
-std=c11 -O3
,它将生成以下x86_64内部循环(部分代码)。.LBB0_7: # =>This Inner Loop Header: Depth=1
movups (%rcx,%rax), %xmm0
movups 16(%rcx,%rax), %xmm1
movups (%rdx,%rax), %xmm2
movups 16(%rdx,%rax), %xmm3
movups %xmm2, (%rcx,%rax)
movups %xmm3, 16(%rcx,%rax)
movups %xmm0, (%rdx,%rax)
movups %xmm1, 16(%rdx,%rax)
movups 32(%rcx,%rax), %xmm0
movups 48(%rcx,%rax), %xmm1
movups 32(%rdx,%rax), %xmm2
movups 48(%rdx,%rax), %xmm3
movups %xmm2, 32(%rcx,%rax)
movups %xmm3, 48(%rcx,%rax)
movups %xmm0, 32(%rdx,%rax)
movups %xmm1, 48(%rdx,%rax)
addq $64, %rax
addq $2, %rsi
jne .LBB0_7
相比之下,使用相同标志的gcc 7.2.0也会进行矢量化,但循环展开较少:
.L7:
movdqa (%rcx,%rax), %xmm0
addq $1, %r9
movdqu (%rdx,%rax), %xmm1
movaps %xmm1, (%rcx,%rax)
movups %xmm0, (%rdx,%rax)
addq $16, %rax
cmpq %r9, %rbx
ja .L7
您最好的选择是最大化寄存器使用,这样当您读取临时变量时,您不会得到额外的(可能被缓存的)内存访问。寄存器数量将取决于系统,而寄存器分配(将变量映射到实际寄存器的逻辑)将取决于编译器。因此,您最好的选择是只期望一个寄存器,并期望其大小与指针相同。这归结为处理解释为size_t
数组块的简单for循环。
size_t[]
。 - Steve JessopWord writes(单词写入)将是最快的。但是,需要考虑块大小和对齐方式。在实践中,通常会合理对齐,但不应该依赖于它。 memcpy()
可以安全地处理所有内容,并且可以针对常数大小进行专门优化(内置)。
以下是一个可移植的解决方案,在大多数情况下表现得 相当不错。
static void swap_byte(void* a, void* b, size_t count)
{
char* x = (char*) a;
char* y = (char*) b;
while (count--) {
char t = *x; *x = *y; *y = t;
x += 1;
y += 1;
}
}
static void swap_word(void* a, void* b, size_t count)
{
char* x = (char*) a;
char* y = (char*) b;
long t[1];
while (count--) {
memcpy(t, x, sizeof(long));
memcpy(x, y, sizeof(long));
memcpy(y, t, sizeof(long));
x += sizeof(long);
y += sizeof(long);
}
}
void memswap(void* a, void* b, size_t size)
{
size_t words = size / sizeof(long);
size_t bytes = size % sizeof(long);
swap_word(a, b, words);
a = (char*) a + words * sizeof(long);
b = (char*) b + words * sizeof(long);
swap_byte(a, b, bytes);
}
这个速度在一定程度上取决于平台,只有通过测试才能真正得出结论。
个人倾向于创建一个与数组大小相等的内存块;使用memcpy交换内容,使用新创建的内存块作为交换空间。
现在,内存块的大小将影响操作的速度(同样取决于平台),因此您可能会发现,对于非常大的数组,来回交换较小量的数据比每次交换大块更快。
编辑
鉴于评论,请让我解释一下,关于交换较小量数据的最后一条评论。
您的目标是使用临时交换空间tmp
将a
数据传输到b
,并将b
数据传输到a
。
tmp
的大小等于或小于a
或b
的大小,并且随着tmp
的减少,交换数据的迭代次数增加,例如,如果tmp
是a
的十分之一,则需要10次迭代。
现在为了加快memcpy的速度,最好确保数组(a、b和tmp)分配对齐的内存空间。
a
复制到 tmp
,然后将整个 b
复制到 a
,最后将整个 tmp
复制到 a
?从缓存的角度来看,这样做效率不高。 - Oliver Charlesworth显然,您必须将A复制到Temp,将B复制到A,然后将Temp复制到B。您可以一次性完成此操作,用于小范围,或者对于大范围,可以分段执行此操作,其中您不希望分配如此大的Temp值。选择段大小取决于您,尽管考虑适合硬件的对齐和缓存问题,对于大型、频繁的移动非常重要。
(实际上还有另一种方法,不需要任何临时空间:将A与B异或,然后将B与A异或,最后将A与B异或。这是老一辈汇编程序员的技巧。)
您可以使用此处描述的逻辑。这样,您可以节省第三个缓冲区。
#include <stddef.h>
#include <stdint.h>
void swap(uint8_t *a, uint8_t *b, size_t length) {
size_t i;
for (i=0; i<length; i++) {
uint8_t aa = a[i];
aa^=b[i];
b[i]^=aa;
aa^=b[i];
a[i] = aa;
}
}
即使只有这一个临时变量,也足以帮助编译器进行优化。
但是如果您使用这样的临时变量,您也可以做到同样的事情
#include <stddef.h>
#include <stdint.h>
void swap(uint8_t *a, uint8_t *b, size_t length) {
size_t i;
for (i=0; i<length; i++) {
uint8_t aa = a[i];
a[i] = b[i];
b[i] = aa;
}
}
乍一看,它们两个都因为访问了很多数组(在第一种情况下)和每次循环只处理一个字节而显得昂贵,但是如果你让编译器优化这个问题,那应该没问题,因为(至少gcc)足够聪明,可以将4个步骤(在x64中:甚至16个步骤)捆绑成一个循环运行。
请注意,您的编译器可能不会如此积极地进行优化,因此您可能需要自己进行拆分。在这种情况下,请注意对齐。
#include <string.h>
#include <stdio.h>
static void swap_elements_of_array(void* base, size_t size_of_element, int a, int b);
static void swap_elements_of_array(void* base, size_t size_of_element, int a, int b)
{
union {
int i; /* force alignment */
char zzz[size_of_element] ; /* VLA */
} swap;
memcpy (swap.zzz, (char*)base + a * size_of_element,size_of_element);
memcpy ((char*)base + a * size_of_element,(char*)base + b * size_of_element,size_of_element);
memcpy ((char*)base + b * size_of_element, swap.zzz, size_of_element);
}
int main (void)
{
unsigned idx,array[] = {0,1,2,3,4,5,6,7,8,9};
swap_elements_of_array(array, sizeof array[0], 2, 5);
for (idx=0; idx < 10; idx++) {
printf( "%u%c", array[idx], (idx==9) ? '\n' : ' ' );
}
return 0;
}
size_of_element
很大,从缓存的角度来看,这看起来并不是很高效,除非编译器足够聪明以交错执行memcpy
。 - Oliver Charlesworth我想分享一下我在微控制器上使用已经很久的简单解决方案,没有任何问题。
#define swap(type, x, y) { type _tmp; _tmp = x; x = y; y = _tmp; }
好的...它创建了一个堆栈变量,但通常用于uint8_t、uint32_t、float、double等。然而,它同样适用于结构体。
编译器应该足够聪明,能够看到当类型大小允许时,堆栈变量可以被交换为寄存器。
真正只适用于小型类型...这将适用于99%的情况。
也可以使用"auto"代替传递类型...但我更喜欢更灵活,我想"auto"可以作为类型传递。
例子...
swap(uint8_t, var1, var2)
swap(float, fv1, fv2)
swap(uint32_t, *p1, *p2) // will swap the contents as p1 and p2 are pointers
swap(auto, var1, var2) // should work fine as long as var1 and var2 are same type
a
和b
是要交换的元素的索引吗? - Didier Trosset