【问题标题】:hardware transactional memory: _xbegin() return 0硬件事务内存:_xbegin() 返回 0
【发布时间】:2023-03-21 18:25:01
【问题描述】:

通过 gcc 文档:x86-transactional-memory-intrinsics.html,当事务失败/中止时,_xbegin() 应该返回 中止状态。但是,我发现它有时会返回 0。而且频率非常高。 **_xbegin()**会在什么情况下返回0?

查手册后,我发现很多情况都可能导致这个结果。例如,CPUID、SYSTEMCALL、CFLUSH.等。但是,我认为我的代码没有触发其中任何一个。

这是我的代码:模拟一家小银行,一个随机账户将 1 美元转移到另一个账户。

#include "immintrin.h"
#include <thread>
#include <unistd.h>
#include <iostream>

using namespace std;

#define n_threads 1
#define OPSIZE 1000000000
typedef struct Account{
    long balance;
    long number;
} __attribute__((aligned(64))) account_t;

typedef struct Bank{
    account_t* accounts;
    long size;
} bank_t;

bool done = 0;
long *tx, *_abort, *capacity, *debug, *failed, *conflict, *zero;

void* f1(bank_t* bank, int id){
    for(int i=0; i<OPSIZE; i++){ 
        int src = rand()%bank->size;
        int dst = rand()%bank->size;
        while(src == dst){
            dst = rand()%bank->size;
        } 

        while(true){
            unsigned stat =  _xbegin();
            if(stat == _XBEGIN_STARTED){
                bank->accounts[src].balance++;  
                bank->accounts[dst].balance--;
                _xend();
                asm volatile("":::"memory");    
                tx[id]++;
                break;
            }else{
                _abort[id]++;

                if (stat == 0){
                    zero[id]++;
                }
                if (stat & _XABORT_CONFLICT){
                    conflict[id]++;
                }
                if (stat & _XABORT_CAPACITY){
                    capacity[id]++;
                }
                if (stat & _XABORT_DEBUG){
                    debug[id]++;
                }
                if ((stat & _XABORT_RETRY) == 0){
                    failed[id]++;
                    break;
                }
                if (stat & _XABORT_NESTED){
                    printf("[ PANIC ] _XABORT_NESTED\n");
                    exit(-1);
                }
                if (stat & _XABORT_EXPLICIT){
                    printf("[ panic ] _XBEGIN_EXPLICIT\n");
                    exit(-1);
                }
            }
        }
    }
    return NULL;
}
void* f2(bank_t* bank){
    printf("_heartbeat function\n");
    long last_txs=0, last_aborts=0, last_capacities=0, last_debugs=0, last_faileds=0, last_conflicts=0, last_zeros = 0;
    long txs=0, aborts=0, capacities=0, debugs=0, faileds=0, conflicts=0, zeros = 0;
    while(1){
        last_txs = txs;
        last_aborts = aborts;
        last_capacities = capacities;
        last_debugs = debugs;
        last_conflicts = conflicts;
        last_faileds = faileds;
        last_zeros = zeros;

        txs=aborts=capacities=debugs=faileds=conflicts=zeros = 0;
        for(int i=0; i<n_threads; i++){
            txs += tx[i];
            aborts += _abort[i];
            faileds += failed[i];
            capacities += capacity[i];
            debugs += debug[i];
            conflicts += conflict[i];
            zeros += zero[i];
        }

        printf("txs\t%ld\taborts\t\t%ld\tfaileds\t%ld\tcapacities\t%ld\tdebugs\t%ld\tconflit\t%ld\tzero\t%ld\n", 
            txs - last_txs, aborts - last_aborts , faileds - last_faileds, 
            capacities- last_capacities, debugs - last_debugs, conflicts - last_conflicts,
            zeros- last_zeros);

        sleep(1);
    }
}

int main(int argc, char** argv){
    int accounts = 10240;

    bank_t* bank = new bank_t;
    bank->accounts = new account_t[accounts];
    bank->size = accounts;

    for(int i=0; i<accounts; i++){
        bank->accounts[i].number = i;
        bank->accounts[i].balance = 0;
    }

    thread* pid[n_threads];
    tx = new long[n_threads];
    _abort = new long[n_threads];
    capacity = new long[n_threads];
    debug = new long[n_threads];
    failed = new long[n_threads];
    conflict = new long[n_threads];
    zero = new long[n_threads];

    thread* _heartbeat = new thread(f2, bank);
    for(int i=0; i<n_threads; i++){
        tx[i] = _abort[i] = capacity[i] = debug[i] = failed[i] = conflict[i] = zero[i] =  0;
        pid[i] = new thread(f1, bank, i);
    }

//  sleep(5);
    for(int i=0; i<n_threads;i++){
        pid[i]->join();
    }
    return 0;
}

补充:

  1. 所有帐户都是 64 位对齐的。我打印了银行->账户[0],银行->账户1地址。 0xf41080,0xf410c0。
  2. 使用 -O0 和 asm volatile("":::"memory");,因此不会出现指令重新排序问题。
  3. 中止率随时间增加。这是结果

    txs     84      aborts          0       faileds 0       capacities      0     debugs  0       conflit 0       zero    0
    txs     17070804      aborts          71      faileds 68      capacities      9       debugs  0       conflit 3       zero    59
    txs     58838         aborts          9516662 faileds 9516661 capacities      0       debugs  0       conflit 1       zero    9516661
    txs     0             aborts          9550428 faileds 9550428 capacities      0       debugs  0       conflit 0       zero    9550428
    txs     0             aborts          9549254 faileds 9549254 capacities      0       debugs  0       conflit 0       zero    9549254
    
  4. 即使 n_threads 为 1,结果也是一样的。

  5. 如果我在回退后添加粗锁,结果似乎是正确的。

    int fallback_lock;
    
    bool 
    rtm_begin(int id)
    {   
        while(true) { 
            unsigned stat;
            stat = _xbegin ();
            if(stat == _XBEGIN_STARTED) {
                return true;
            } else {
                _abort[id]++;
                if (stat == 0){
                    zero[id]++;
                }
                //call some fallback function
                if (stat& _XABORT_CONFLICT){
                    conflict[id]++;
                }
    
                //will not succeed on a retry
                if ((stat &  _XABORT_RETRY) == 0) {
                    failed[id]++;
                    //grab a fallback lock
                    while (!__sync_bool_compare_and_swap(&fallback_lock,0,1)) {
                    }
                    return false;
                }
            }
        }
    }
    ....
    
    in_rtm = rtm_begin(id);
    y = fallback_lock;
    accounts[src].balance--;
    accounts[dst].balance++;
    if (in_rtm){
        _xend();
    }else{
        while(!__sync_bool_compare_and_swap(&fallback_lock, 1, 0)){
        }
    }
    

【问题讨论】:

  • 嗯。粗略的检查表明一切都很好(不过,我还没有运行它,而且自从我积极与 TM 合作以来已经有一段时间了)。 1 个线程的失败似乎很奇怪——如果将线程固定到核心,它仍然会失败吗?同样,粗锁结果有点奇怪。如果您改为退出 (spinwait) 会发生什么?
  • 即使将线程固定到核心,它也会失败。有人认为它可能是因为 tlb 丢失或缓存丢失。因为事务中止会回到它的起点,就好像中断永远不会发生一样。所以下次会发生错误。
  • 这看起来非常尴尬。 :S 抱歉,唉,我想我们在这里已经用尽了我的专业知识!
  • 您使用的是哪个处理器? TSX 有问题,在微码更新后已在 Haswell、Haswell-E、Haswell-EP 和早期的 Broadwell CPU 上禁用。

标签: c++ x86 transactional-memory intel-tsx


【解决方案1】:

RTM 上的硬件文档建议如下:

在 RTM 中止后,EAX 的值可以为“0”。例如,在 RTM 区域内使用 CPUID 指令会导致事务中止,并且可能不满足设置任何 EAX 位的要求。这可能导致 EAX 值为“0”。

(其中,EAX是用来传递状态的硬件寄存器,GCC会依次返回给你作为返回值)

【讨论】:

  • 感谢您的回答,但我认为 CPUID 或 SYSTEMCALL 不会导致问题。我重新编辑了我的问题并触摸了整个代码。你能解释一下为什么需要备用粗锁吗?
  • 看起来有很多事情导致aborts
猜你喜欢
  • 1970-01-01
  • 2014-04-30
  • 1970-01-01
  • 1970-01-01
  • 2013-03-19
  • 2011-06-08
  • 1970-01-01
  • 2016-02-20
  • 2013-04-20
相关资源
最近更新 更多