تحقيق وظيفة GCC GCC للإصدار 4.1.2 والإصدارات الأقدم

0

مشروع شركتي الجديدة ، يريدون تشغيل الكود 32 بت ، خادم الترجمة هو CentOS 5.0 مع GCC 4.1.1 ، كان هذا الكابوس.
هناك الكثير من الوظائف التي تستخدم في المشروع مثل __sync_fetch_and_add أعطيت في دول مجلس التعاون الخليجي 4.1.2 وما بعدها.

قيل لي لا يمكنني ترقية إصدار GCC ، لذلك يجب أن أقوم بعمل حل آخر بعد Googling لعدة ساعات.

عندما كتبت عرضًا تجريبيًا للاختبار ، تلقيت للتو إجابة خاطئة ، وتريد ضربة الشفرة استبدال الوظيفة __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;
}

عندما حصلت على النتيجة الخاطئة:

[[email protected] workspace]# ./asm
17123
[[email protected] workspace]# ./asm
14670
[[email protected] workspace]# ./asm
14604
[[email protected] workspace]# ./asm
13837
[[email protected] workspace]# ./asm
14043
[[email protected] workspace]# ./asm
16160
[[email protected] workspace]# ./asm
15271
[[email protected] workspace]# ./asm
15280
[[email protected] workspace]# ./asm
15465
[[email protected] 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;
}

ولكن عندما أقوم بتشغيل هذا الرمز باستخدام ./asm بعد g ++ -g -o asm asm.cpp -lpthread.the عالق للتو لأكثر من 5 دقائق ، انظر أعلى في محطة طرفية أخرى:

3861 root 19 0210m 888732 S 400 0.0 2: 51.06 asm

أنا فقط مرتبك ، أليس هذا الرمز هو نفسه؟

3 الاجابة

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 5.4.0 و GCC-4.9.3 لـ x86 و x86-64 (باستخدام -O2 ) على لينكس.

إذا وجدت أي أخطاء أو مشكلات في أعلاه ، فيرجى إبلاغي بذلك في تعليق حتى أتمكن من التحقق والإصلاح حسب الحاجة.

:مؤلف
1
افضل جواب

نتيجتك تبدو لي. تنجح معظم الوقت ، لكنها ستفشل إذا ضربك نواة أخرى لكمة. أنت تفعل 20 ألف محاولة لـ cmpxchg ، وليس 20 ألف زيادات ذرية.

لأكتب مع asm مضمنة ، ستحتاج إلى استخدامها . تم تصميمه خصيصًا لتنفيذ الجلب والإضافة.

يتطلب تنفيذ عمليات أخرى ، مثل الجلب أو الجلب ، حلقة CAS لإعادة المحاولة إذا كنت تحتاج بالفعل إلى القيمة القديمة. لذا يمكنك عمل نسخة من الوظيفة لا تعيد القيمة القديمة ، وهي مجرد بدون الجلب ، باستخدام مع وجهة ذاكرة. (يمكن أن يقوم برنامج Compiler المدمج بعمل هذا التحسين بناءً على ما إذا كانت النتيجة مطلوبة أم لا ، ولكن تطبيق ASM المضمّن لا يحصل على فرصة لاختيار asm بناءً على تلك المعلومات.)

من أجل الكفاءة ، تذكر ذلك ، ، والعديد من التعليمات الأخرى يمكن أن تستخدم المعاملات الفورية ، لذلك أ القيد سيكون مناسبا (لا إلى عن على على x86-64 ، لأن ذلك سيسمح للمبتدئين كبيرًا جدًا. https://gcc.gnu.org/onlinedocs/gcc/Machine-Constictions.html ). ولكن لا يمكن استخدام cmpxchg و xadd و xchg بشكل فوري بالطبع.

أقترح النظر في إخراج المترجم لـ gcc الحديث (على سبيل المثال على http://godbolt.org/ ) للوظائف التي تستخدم المضمنة ، لمعرفة ما يفعله المترجمون.


ولكن احذر من أن asm المضمنة يمكن أن تجمع بشكل صحيح بالنظر إلى مجموعة واحدة من التعليمات البرمجية المحيطة ، ولكن ليس بالطريقة التي تتوقعها بالنظر إلى رمز مختلف. على سبيل المثال ، إذا قام الكود المحيط بنسخ قيمة بعد استخدام CAS عليه (ربما غير مرجح) ، فقد يقرر المترجم إعطاء قالب asm معاملي ذاكرة مختلفين لـ و ، ولكن قالب asm الخاص بك يفترض أنه سيكون دائمًا نفس العنوان.

IDK إذا كان gcc4.1 يدعم ذلك ، ولكن ستعلن معامل ذاكرة القراءة / الكتابة . خلاف ذلك ربما يمكنك استخدام قيد مطابق لقول أن الإدخال في نفس الموقع مثل معامل سابق ، مثل . ولكن هذا قد يعمل فقط للسجلات ، وليس للذاكرة ، لم أتحقق.


هو خطأ: يكتب cmpxchg EAX عند الفشل .

ليس من المقبول أن تخبر المترجم أنك تترك reg دون تعديل ، ثم اكتب قالب asm يقوم بتعديله. ستحصل على سلوك لا يمكن التنبؤ به من الاصطدام بأصابع المترجم.

راجع التجميع المضمّن c الحصول على "عدم تطابق حجم المعامل" عند استخدام cmpxchg للحصول على غلاف آمن مضمّن لـ . إنه مكتوب لـ gcc6 flag-output ، لذلك سيكون عليك دعم ذلك وربما بعض تفاصيل بناء الجملة الأخرى إلى gcc4.1 القديم.

تتناول هذه الإجابة أيضًا إعادة القيمة القديمة ، لذا لا يجب تحميلها بشكل منفصل.

(يبدو استخدام gcc4.1 القديم فكرة سيئة بالنسبة لي ، خاصةً لكتابة كود متعدد الخيوط. هناك مساحة كبيرة للخطأ من نقل كود العمل مع مدمجة إلى ASM ملفوف يدويًا. مخاطر استخدام مترجم جديد ، مثل gcc5.5 المستقر إن لم يكن gcc7.4 ، مختلفة ولكن ربما تكون أصغر.)

إذا كنت تنوي إعادة كتابة التعليمات البرمجية باستخدام مدمجة ، سيكون الشيء المعقول إعادة كتابته باستخدام C11 أو GNU C أكثر حداثة مدمجة تهدف إلى استبدالها .

نواة لينكس تستخدم بنجاح asm مضمنة للذرات الملفوفة يدويًا ، على الرغم من ذلك ، فمن الممكن بالتأكيد.

:مؤلف
1
افضل جواب

64 بت compare_and_swap خطأ لأنه يتبادل 64 بت ولكن int هو 32 بت فقط.

compare_and_swap يجب استخدامها في حلقة تعيد المحاولة حتى تنجح.

:مؤلف
فوق
قائمة طعام