All streams
Search
Write a publication
Pull to refresh
-11
@0xtcnonrread⁠-⁠only

User

Send message
Потому что количество атомарных операций пропорционально числу потоков, которым делегируется выполнение задачи. Но несколько атомарных операций параллельно выполняться не может, они выстроятся в очередь.

В очередь/не очередь — это ничего не меняет, ведь никто не говорил про параллельность. Вы говорили о замедлении, а из очереди замедление никак не следует. К тому же, откуда взялась эта очередь? Пруфы так и не появились.

См. мануал по префиксу LOCK.

От 386го(судя по «шина» — это действительно так)? Можно ссылку?

Всё воспроизводится.

Где? То, что там 6нс — это просто оно долбит в l1d. Это очевидно поведение, чем дальше оно изменяет данные — тем дальше происходит инвалидация. Но всё это к делу не относится.

Вне зависимости от числа потоков время на одну атомарную операцию увеличивается, либо остаётся тем же.

Где у вас было про «остаётся», покажите? Или это попытка задним числом подменить тезис?

Если у нас 3 потока — первый увидит задание через 20 нс, второй — через 40 нс, третий — через 60 нс и т.д. А потом ещё собирать статус об завершении выполнения заданий. Но тут одновременного завершения не будет, поэтому все скорее всего попадут на 5 нс.

У нас три потока. Замедления не обнаружено(вернее не замедления, а линейное падение производительности в зависимости от кол-ва потоков).

В любом случае, всё это выглядит как бред. Что такое 3 потока? Каким образом это на что-то влияет? А если у нас будет 3 потока и три атомика?

В любом случае, эта гипотеза уже была разоблачена выше, т.к. в этом бенчмарке атомик шарит множество тредов и никакой нелепой очереди вида «обновили значение в одном потоке, обновили в другом» — тут нет и не будет. Это базовая семантика атомика — он лишь обновляет ближайшую общую память, а далее ничего никуда не блокируется, а обновляется базовыми механизмами обеспечения когерентности.

using int16_vec_t = int16_t __attribute__((__vector_size__(16)));

auto vhadd(int16_vec_t a, int16_vec_t b) {
  return __builtin_ia32_phaddw128(a, b);
}

auto vhsumm(int16_vec_t v) {
  v = vhadd(v, v);
  v = vhadd(v, v);
  v = vhadd(v, v);
  return v;
}

auto summ(int16_vec_t v) {
  return v[0] + v[1] + v[2] + v[3] + v[4] + v[5] + v[6] + v[7];
}



static void BM_SSE_COUNT_NG_HSUMM(benchmark::State &state) {
  
  for(auto _: state) {
    auto cnt = int16_vec_t{} - 1;
    auto it = (int16_vec_t *)allignedArr, end = (int16_vec_t *)(allignedArr + ARR_SIZE);
    while(it < end) {
      cnt += (*it == VAL); ++it;
      cnt += (*it == VAL); ++it;
      cnt += (*it == VAL); ++it;
      cnt += (*it == VAL); ++it;
    }
    cnt = -1 - cnt;
    auto res = vhsumm(cnt)[0];
    benchmark::DoNotOptimize(res);
  }
}

BENCHMARK(BM_SSE_COUNT_NG_HSUMM);


static void BM_SSE_COUNT_NG_NAIVESUMM(benchmark::State &state) {
  
  for(auto _: state) {
    auto cnt = int16_vec_t{};
    auto it = (int16_vec_t *)allignedArr, end = (int16_vec_t *)(allignedArr + ARR_SIZE);
    while(it < end) {
      cnt += (*it == VAL) & 1; ++it;
      cnt += (*it == VAL) & 1; ++it;
      cnt += (*it == VAL) & 1; ++it;
      cnt += (*it == VAL) & 1; ++it;
    }
    auto res = summ(cnt);
    benchmark::DoNotOptimize(res);
  }
}

BENCHMARK(BM_SSE_COUNT_NG_NAIVESUMM);


оптимизатор это превратил в (vpcmpeqw+vpsubw) на каждые 16 uint16_t.


Действительно, гцц осилил превратить превратить второй в подобие первого решения.

согласно спеке throughput = 0.5 + 0.33 (предполагаем зависимость).

Это тут непричём.

Общее время — (0.5 + 0.33) * 1024 / 16 / 3.2 = 16.6ns, что очень похоже на правду.

Абсолютно неверно. Никакие трупуты не складываются, особенно так колхозно.

cnt += (*it == VAL) & 1; ++it;

Это зависимое днище, оно будет упираться в летенси vpsubw, который(очевидно) 1такт. Остальное стоит ноль. Отклонения от 20(вниз) — это лишь следствие реорганизации вычислений.
атомарные операции блокируют шину

Какую шину?

То есть один поток зависнет на одной операции на 5 нс, два потока — по 10 нс каждый и т.д.

Почему?

итого +40 нс на накладные расходы в идеальном случае.

Не воспроизводится, вот бенчмарк:

#include <atomic>
#include <chrono>
#include <thread>

constexpr auto n = 1e9;
std::atomic<size_t> an = n;

namespace chrono = std::chrono;
auto start = chrono::high_resolution_clock::now();

void stop() {
  auto time = chrono::nanoseconds(chrono::high_resolution_clock::now() - start).count();
  fprintf(stderr, "%fns/dec\n", time / n);
  std::exit(0);
}

void work() {
  while(true) {
    if(!an--) stop();
  }
}


int main(int argc, char * argv[]) {
  size_t threads = (argv[1]) ? std::stoul(argv[1]): 1;
  while(--threads)
    std::thread{work}.detach();
  work();
}


Всё это бесполезно, симдовое cmp намного мощнее. Что угодно там колхозь — даже на 64 битных симдах оно будет быстрее.
2

Information

Rating
Does not participate
Registered
Activity