我的新公司项目,他们希望代码运行在32位,编译服务器是带有 GCC 4.1.1 的 CentOS 5.0 。
在项目中使用的功能很多,例如__sync_fetch_and_add
在 GCC 4.1.2 及更高版本中给出。
有人告诉我无法升级GCC版本,因此在谷歌搜索了几个小时后,我不得不提出另一个解决方案。
当我编写演示进行测试时,我得到的答案是错误的,代码打击想替换功能__sync_fetch_and_add
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
static int count = 0;
int compare_and_swap(int* reg, int oldval, int newval)
{
register char result;
#ifdef __i386__
__asm__ volatile ("lock; cmpxchgl %3, %0; setz %1"
: "=m"(*reg), "=q" (result)
: "m" (*reg), "r" (newval), "a" (oldval)
: "memory");
return result;
#elif defined(__x86_64__)
__asm__ volatile ("lock; cmpxchgq %3, %0; setz %1"
: "=m"(*reg), "=q" (result)
: "m" (*reg), "r" (newval), "a" (oldval)
: "memory");
return result;
#else
#error:architecture not supported and gcc too old
#endif
}
void *test_func(void *arg)
{
int i = 0;
for(i = 0; i < 2000; ++i) {
compare_and_swap((int *)&count, count, count + 1);
}
return NULL;
}
int main(int argc, const char *argv[])
{
pthread_t id[10];
int i = 0;
for(i = 0; i < 10; ++i){
pthread_create(&id[i], NULL, test_func, NULL);
}
for(i = 0; i < 10; ++i) {
pthread_join(id[i], NULL);
}
//10*2000=20000
printf("%d\n", count);
return 0;
}
如果我得到了错误的结果:
[root@centos-linux-7 workspace]# ./asm
17123
[root@centos-linux-7 workspace]# ./asm
14670
[root@centos-linux-7 workspace]# ./asm
14604
[root@centos-linux-7 workspace]# ./asm
13837
[root@centos-linux-7 workspace]# ./asm
14043
[root@centos-linux-7 workspace]# ./asm
16160
[root@centos-linux-7 workspace]# ./asm
15271
[root@centos-linux-7 workspace]# ./asm
15280
[root@centos-linux-7 workspace]# ./asm
15465
[root@centos-linux-7 workspace]# ./asm
16673
我在这行中意识到
compare_and_swap((int *)&count, count, count + 1);
count + 1
是错的!
然后如何实现与__sync_fetch_and_add
相同的功能。当第三个参数恒定时,compare_and_swap
函数将起作用。
顺便说一句,compare_and_swap
函数对吗?我只是为此搜索Google,不熟悉汇编程序。
我对这个问题感到绝望。
……………………………………………………………………………………………………………… ……………………………………
看到下面的答案后,我用了一会儿就得到了正确的答案,但似乎更加困惑。 这是代码:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
static unsigned long count = 0;
int sync_add_and_fetch(int* reg, int oldval, int incre)
{
register char result;
#ifdef __i386__
__asm__ volatile ("lock; cmpxchgl %3, %0; setz %1" : "=m"(*reg), "=q" (result) : "m" (*reg), "r" (oldval + incre), "a" (oldval) : "memory");
return result;
#elif defined(__x86_64__)
__asm__ volatile ("lock; cmpxchgq %3, %0; setz %1" : "=m"(*reg), "=q" (result) : "m" (*reg), "r" (newval + incre), "a" (oldval) : "memory");
return result;
#else
#error:architecture not supported and gcc too old
#endif
}
void *test_func(void *arg)
{
int i=0;
int result = 0;
for(i=0;i<2000;++i)
{
result = 0;
while(0 == result)
{
result = sync_add_and_fetch((int *)&count, count, 1);
}
}
return NULL;
}
int main(int argc, const char *argv[])
{
pthread_t id[10];
int i = 0;
for(i=0;i<10;++i){
pthread_create(&id[i],NULL,test_func,NULL);
}
for(i=0;i<10;++i){
pthread_join(id[i],NULL);
}
//10*2000=20000
printf("%u\n",count);
return 0;
}
答案正好是20000,所以我认为当您使用sync_add_and_fetch函数时,应该使用while循环是愚蠢的,所以我这样写:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
static unsigned long count = 0;
int compare_and_swap(int* reg, int oldval, int incre)
{
register char result;
#ifdef __i386__
__asm__ volatile ("lock; cmpxchgl %3, %0; setz %1" : "=m"(*reg), "=q" (result) : "m" (*reg), "r" (oldval + incre), "a" (oldval) : "memory");
return result;
#elif defined(__x86_64__)
__asm__ volatile ("lock; cmpxchgq %3, %0; setz %1" : "=m"(*reg), "=q" (result) : "m" (*reg), "r" (newval + incre), "a" (oldval) : "memory");
return result;
#else
#error:architecture not supported and gcc too old
#endif
}
void sync_add_and_fetch(int *reg,int oldval,int incre)
{
int ret = 0;
while(0 == ret)
{
ret = compare_and_swap(reg,oldval,incre);
}
}
void *test_func(void *arg)
{
int i=0;
for(i=0;i<2000;++i)
{
sync_add_and_fetch((int *)&count, count, 1);
}
return NULL;
}
int main(int argc, const char *argv[])
{
pthread_t id[10];
int i = 0;
for(i=0;i<10;++i){
pthread_create(&id[i],NULL,test_func,NULL);
}
for(i=0;i<10;++i){
pthread_join(id[i],NULL);
}
//10*2000=20000
printf("%u\n",count);
return 0;
}
但是当我在g ++ -g -o asm asm.cpp -lpthread之后用./asm运行此代码时,asm停留了5分钟以上,请参见另一个终端的顶部:
3861根19 0 102m 888732 S 400 0.0 2:51.06 asm
我只是感到困惑,这段代码不一样吗?
答案 0 :(得分:1)
64位的compare_and_swap
是错误的,因为它交换了64位,而int只有32位。
compare_and_swap
应该在重试它直到成功的循环中使用。
答案 1 :(得分:1)
您的结果对我来说不错。 lock cmpxchg
在大多数情况下都是成功的,但是如果另一个核心击败了您,它将失败。您正在尝试20万次尝试cmpxchg count+1
,而不是20k原子增量。
要使用内联汇编编写__sync_fetch_and_add
,则需要使用lock xadd
。它是专门为实现fetch-add而设计的。
要实现其他操作(如fetch-or或fetch-and),则需要CAS重试循环,如果您实际上需要旧值。因此,您可以使用sync-and
和内存目标,使该函数的版本不返回旧值,并且是 just 一个lock and
,而无需提取。 (编译器内置的文件可以根据是否需要结果来进行优化,但是内联asm实现没有机会根据该信息选择asm。)
为了提高效率,请记住and
,or
,add
和许多其他指令可以使用立即数操作数,因此使用"re"(src)
约束是合适的(而不是{{1 }}在x86-64上用于"ri"
,因为这会使立即数太大。https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html)。但是,cmpxchg,xadd和xchg当然不能使用立即数。
我建议您查看使用内置函数的现代gcc的编译器输出(例如,在http://godbolt.org/上),以查看编译器的功能。
但是要注意,内联汇编程序可以在一组周围的代码下正确编译,但不能在给定不同代码的情况下正确编译。例如如果周围的代码在使用CAS后复制了一个值(可能不太可能),则编译器可能会决定为int64_t
和"=m"(*reg)
给asm模板两个不同的内存操作数,但是您的asm模板假定它们会总是相同的地址。
IDK(如果gcc4.1支持),但是 "m"(*reg)
将声明读/写内存操作数。否则,也许您可以使用匹配约束来说输入与早期操作数(例如"+m"(*reg)
)位于同一位置。但这可能仅适用于寄存器,而不适用于内存,我没有检查。
"0"(*reg)
是一个错误:cmpxchg writes EAX on failure。告诉编译器您未修改reg,然后编写可对其进行修改的asm模板是不正确的。踩到编译器的脚趾,您将得到无法预测的行为。
有关"a" (oldval)
的安全内联汇编包装,请参见c inline assembly getting "operand size mismatch" when using cmpxchg 。它是为gcc6标志输出编写的,因此您必须将其以及可能的其他一些语法详细信息回传到硬壳的旧gcc4.1。
该答案还解决了返回旧值的问题,因此不必单独加载。
(对于我来说,使用古老的gcc4.1听起来是个坏主意,特别是对于编写多线程代码。从将带有lock cmpxchg
内建函数的工作代码移植到手动滚动的asm上,错误空间很大。使用更新的编译器,例如稳定的gcc5.5(如果不是gcc7.4),则有所不同,但可能会更小。)
如果您打算使用__sync
内置程序来重写代码,那么理智的事情就是使用C11 __sync
或GNU C更现代的stdatomic.h
内置程序来重写代码。替换__atomic
。
尽管如此,Linux内核确实成功地将内联asm用于手动滚动原子,所以这肯定是可能的。
答案 2 :(得分:1)
如果您确实处于这种困境中,那么我将从以下头文件开始:
#ifndef SYNC_H
#define SYNC_H
#if defined(__x86_64__) || defined(__i386__)
static inline int sync_val_compare_and_swap_int(int *ptr, int oldval, int newval)
{
__asm__ __volatile__( "lock cmpxchgl %[newval], %[ptr]"
: "+a" (oldval), [ptr] "+m" (*ptr)
: [newval] "r" (newval)
: "memory" );
return oldval;
}
static inline int sync_fetch_and_add_int(int *ptr, int val)
{
__asm__ __volatile__( "lock xaddl %[val], %[ptr]"
: [val] "+r" (val), [ptr] "+m" (*ptr)
:
: "memory" );
return val;
}
static inline int sync_add_and_fetch_int(int *ptr, int val)
{
const int old = val;
__asm__ __volatile__( "lock xaddl %[val], %[ptr]"
: [val] "+r" (val), [ptr] "+m" (*ptr)
:
: "memory" );
return old + val;
}
static inline int sync_fetch_and_sub_int(int *ptr, int val) { return sync_fetch_and_add_int(ptr, -val); }
static inline int sync_sub_and_fetch_int(int *ptr, int val) { return sync_add_and_fetch_int(ptr, -val); }
/* Memory barrier */
static inline void sync_synchronize(void) { __asm__ __volatile__( "mfence" ::: "memory"); }
#else
#error Unsupported architecture.
#endif
#endif /* SYNC_H */
相同的扩展内联程序集可同时用于x86和x86-64。仅实现int
类型,您确实需要用__sync_synchronize()
替换可能的sync_synchronize()
调用,并用__sync_...()
替换每个sync_..._int()
调用。
要进行测试,您可以使用例如
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include "sync.h"
#define THREADS 16
#define PERTHREAD 8000
void *test_func1(void *sumptr)
{
int *const sum = sumptr;
int n = PERTHREAD;
while (n-->0)
sync_add_and_fetch_int(sum, n + 1);
return NULL;
}
void *test_func2(void *sumptr)
{
int *const sum = sumptr;
int n = PERTHREAD;
while (n-->0)
sync_fetch_and_add_int(sum, n + 1);
return NULL;
}
void *test_func3(void *sumptr)
{
int *const sum = sumptr;
int n = PERTHREAD;
int oldval, curval, newval;
while (n-->0) {
curval = *sum;
do {
oldval = curval;
newval = curval + n + 1;
} while ((curval = sync_val_compare_and_swap_int(sum, oldval, newval)) != oldval);
}
return NULL;
}
static void *(*worker[3])(void *) = { test_func1, test_func2, test_func3 };
int main(void)
{
pthread_t thread[THREADS];
pthread_attr_t attrs;
int sum = 0;
int t, result;
pthread_attr_init(&attrs);
pthread_attr_setstacksize(&attrs, 65536);
for (t = 0; t < THREADS; t++) {
result = pthread_create(thread + t, &attrs, worker[t % 3], &sum);
if (result) {
fprintf(stderr, "Failed to create thread %d of %d: %s.\n", t+1, THREADS, strerror(errno));
exit(EXIT_FAILURE);
}
}
pthread_attr_destroy(&attrs);
for (t = 0; t < THREADS; t++)
pthread_join(thread[t], NULL);
t = THREADS * PERTHREAD * (PERTHREAD + 1) / 2;
if (sum == t)
printf("sum = %d (as expected)\n", sum);
else
printf("sum = %d (expected %d)\n", sum, t);
return EXIT_SUCCESS;
}
不幸的是,我没有要测试的较旧版本的GCC,因此仅在x86和x86-64上使用GCC 5.4.0和GCC-4.9.3(使用-O2
)进行了测试。 Linux。
如果您发现上述任何错误或问题,请在评论中告知我,以便我根据需要进行验证和修复。