-
Notifications
You must be signed in to change notification settings - Fork 0
feat(bench): add vectorized + unrolled optimal version #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dssgabriel
wants to merge
2
commits into
main
Choose a base branch
from
feature/unrolled-vectorized-gemm
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,8 @@ | ||
| // benchmarks/gemm.cpp -*-C++-*- | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| #include <getopt.h> | ||
| #include <immintrin.h> | ||
|
|
||
| #include <cassert> | ||
| #include <chrono> | ||
|
|
@@ -192,9 +193,9 @@ | |
| assert(A.extent(0) == C.extent(0)); | ||
| assert(B.extent(1) == C.extent(1)); | ||
|
|
||
| using ATile = std::mdspan<Type, std::extents<size_t, TM, TO>, std::layout_right>; | ||
| using BTile = std::mdspan<Type, std::extents<size_t, TO, TN>, std::layout_right>; | ||
| using CTile = std::mdspan<Type, std::extents<size_t, TM, TN>, std::layout_right>; | ||
| using ATile = std::mdspan<Type, std::extents<size_t, TM, TO>, typename MdSpanA::mapping_type::inner_layout_type>; | ||
| using BTile = std::mdspan<Type, std::extents<size_t, TO, TN>, typename MdSpanB::mapping_type::inner_layout_type>; | ||
| using CTile = std::mdspan<Type, std::extents<size_t, TM, TN>, typename MdSpanC::mapping_type::inner_layout_type>; | ||
|
|
||
| const size_t M = A.extent(0); | ||
| const size_t N = B.extent(1); | ||
|
|
@@ -228,14 +229,14 @@ | |
|
|
||
| template <size_t TM, size_t TN, size_t TO, class MdSpanA, class MdSpanB, class MdSpanC> | ||
| requires IsMdSpan2D<MdSpanA> && IsMdSpan2D<MdSpanB> && IsMdSpan2D<MdSpanC> | ||
| auto manual_blocked_tiled_subview_BLTL(const MdSpanA A, const MdSpanB B, MdSpanC C) -> void { | ||
| auto manual_blocked_tiled_subview_vec_unroll_k(const MdSpanA A, const MdSpanB B, MdSpanC C) -> void { | ||
| assert(A.extent(1) == B.extent(0)); | ||
| assert(A.extent(0) == C.extent(0)); | ||
| assert(B.extent(1) == C.extent(1)); | ||
|
|
||
| using ATile = std::mdspan<Type, std::extents<size_t, TM, TO>, std::layout_right>; | ||
| using BTile = std::mdspan<Type, std::extents<size_t, TO, TN>, std::layout_left>; | ||
| using CTile = std::mdspan<Type, std::extents<size_t, TM, TN>, std::layout_right>; | ||
| using ATile = std::mdspan<Type, std::extents<size_t, TM, TO>, typename MdSpanA::mapping_type::inner_layout_type>; | ||
| using BTile = std::mdspan<Type, std::extents<size_t, TO, TN>, typename MdSpanB::mapping_type::inner_layout_type>; | ||
| using CTile = std::mdspan<Type, std::extents<size_t, TM, TN>, typename MdSpanC::mapping_type::inner_layout_type>; | ||
|
|
||
| const size_t M = A.extent(0); | ||
| const size_t N = B.extent(1); | ||
|
|
@@ -253,20 +254,147 @@ | |
| for (size_t i = 0; i < std::min(TM, M - x); ++i) { | ||
| for (size_t j = 0; j < std::min(TN, N - y); ++j) { | ||
| Type acc = 0.0; | ||
| #if defined(__AVX__) && defined(__FMA__) | ||
| // 8x unrolled and vectorized kernel (processing 32 elements/iter). | ||
| // If TO == 32, this effectively fully unrolls the `k` loop. | ||
| // | ||
| // On Zen3, `vfmaddXXXpd` is 4 lat / 0.5 tp (CPI) => at least 2 FMAs needed to achieve max throughput. | ||
| // Unrolling more allows to amortize the cost of the final horizontal reduction. | ||
| // | ||
| // TODO(dssgabriel): Attempt to vectorize the `j` loop to remove the need for the horizontal reduction. | ||
| if constexpr (TO >= 32 and TO % 32 == 0) { | ||
| __m256d vacc = _mm256_setzero_pd(); | ||
| for (size_t k = 0; k < std::min(TO, O - z); k += 32) { | ||
| __m256d va0 = _mm256_loadu_pd(&tA[i, k]); | ||
| __m256d vb0 = _mm256_loadu_pd(&tB[k, j]); | ||
| vacc = _mm256_fmadd_pd(va0, vb0, vacc); | ||
| __m256d va1 = _mm256_loadu_pd(&tA[i, k + 4]); | ||
| __m256d vb1 = _mm256_loadu_pd(&tB[k + 4, j]); | ||
| vacc = _mm256_fmadd_pd(va1, vb1, vacc); | ||
| __m256d va2 = _mm256_loadu_pd(&tA[i, k + 8]); | ||
| __m256d vb2 = _mm256_loadu_pd(&tB[k + 8, j]); | ||
| vacc = _mm256_fmadd_pd(va2, vb2, vacc); | ||
| __m256d va3 = _mm256_loadu_pd(&tA[i, k + 12]); | ||
| __m256d vb3 = _mm256_loadu_pd(&tB[k + 12, j]); | ||
| vacc = _mm256_fmadd_pd(va3, vb3, vacc); | ||
| __m256d va4 = _mm256_loadu_pd(&tA[i, k + 16]); | ||
| __m256d vb4 = _mm256_loadu_pd(&tB[k + 16, j]); | ||
| vacc = _mm256_fmadd_pd(va4, vb4, vacc); | ||
| __m256d va5 = _mm256_loadu_pd(&tA[i, k + 20]); | ||
| __m256d vb5 = _mm256_loadu_pd(&tB[k + 20, j]); | ||
| vacc = _mm256_fmadd_pd(va5, vb5, vacc); | ||
| __m256d va6 = _mm256_loadu_pd(&tA[i, k + 24]); | ||
| __m256d vb6 = _mm256_loadu_pd(&tB[k + 24, j]); | ||
| vacc = _mm256_fmadd_pd(va6, vb6, vacc); | ||
| __m256d va7 = _mm256_loadu_pd(&tA[i, k + 28]); | ||
| __m256d vb7 = _mm256_loadu_pd(&tB[k + 28, j]); | ||
| vacc = _mm256_fmadd_pd(va7, vb7, vacc); | ||
| } | ||
| __m128d hsum = _mm_add_pd(_mm256_castpd256_pd128(vacc), _mm256_extractf128_pd(vacc, 1)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. clang-tidy diagnosticbenchmarks/gemm.cpp:293:30: warning: [portability-simd-intrinsics]
293 | __m128d hsum = _mm_add_pd(_mm256_castpd256_pd128(vacc), _mm256_extractf128_pd(vacc, 1));
| ^
/home/runner/work/layout-tiled/layout-tiled/benchmarks/gemm.cpp:627:10: warning: do not declare C-style arrays, use 'std::array' instead [cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays]
627 | static struct option long_options[] = {
| ^
/home/runner/work/layout-tiled/layout-tiled/build/_deps/fmt-src/include/fmt/base.h:1683:5: error: 5 uninitialized fields at the end of the constructor call [clang-analyzer-optin.cplusplus.UninitializedObject,-warnings-as-errors]
1683 | ignore_unused(arg_index, named_arg_index);
| ^
/home/runner/work/layout-tiled/layout-tiled/build/_deps/fmt-src/include/fmt/base.h:520:15: note: uninitialized pointer 'this->context_.parse_context::fmt_.data_'
520 | const Char* data_;
| ^~~~~
/home/runner/work/layout-tiled/layout-tiled/build/_deps/fmt-src/include/fmt/base.h:521:10: note: uninitialized field 'this->context_.parse_context::fmt_.size_'
521 | size_t size_;
| ^~~~~
/home/runner/work/layout-tiled/layout-tiled/build/_deps/fmt-src/include/fmt/base.h:870:7: note: uninitialized field 'this->context_.parse_context::next_arg_id_'
870 | int next_arg_id_;
| ^~~~~~~~~~~~
/home/runner/work/layout-tiled/layout-tiled/build/_deps/fmt-src/include/fmt/base.h:1213:7: note: uninitialized field 'this->context_.num_args_'
1213 | int num_args_;
| ^~~~~~~~~
/home/runner/work/layout-tiled/layout-tiled/build/_deps/fmt-src/include/fmt/base.h:1214:15: note: uninitialized pointer 'this->context_.types_'
1214 | const type* types_;
| ^~~~~~
/home/runner/work/layout-tiled/layout-tiled/benchmarks/gemm.cpp:637:10: note: Assuming the condition is true
637 | while ((opt = getopt_long(argc, argv, "n:t:o:h", static_cast<const struct option*>(long_options), nullptr)) != -1) {
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/runner/work/layout-tiled/layout-tiled/benchmarks/gemm.cpp:637:3: note: Loop condition is true. Entering loop body
637 | while ((opt = getopt_long(argc, argv, "n:t:o:h", static_cast<const struct option*>(long_options), nullptr)) != -1) {
| ^
/home/runner/work/layout-tiled/layout-tiled/benchmarks/gemm.cpp:638:5: note: Control jumps to the 'default' case at line 659
638 | switch (opt) {
| ^
/home/runner/work/layout-tiled/layout-tiled/benchmarks/gemm.cpp:660:18: note: Calling constructor for 'fstring<std::basic_string<char> &>'
660 | fmt::print("{}", HELP_MSG);
| ^~~~
/home/runner/work/layout-tiled/layout-tiled/build/_deps/fmt-src/include/fmt/base.h:2709:5: note: Taking true branch
2709 | if (FMT_USE_CONSTEVAL) parse_format_string<char>(s, checker(s, arg_pack()));
| ^
/home/runner/work/layout-tiled/layout-tiled/build/_deps/fmt-src/include/fmt/base.h:2709:57: note: Calling constructor for 'format_string_checker<char, 1, 0, false>'
2709 | if (FMT_USE_CONSTEVAL) parse_format_string<char>(s, checker(s, arg_pack()));
| ^~~~~~~~~~~~~~~~~~~~~~
/home/runner/work/layout-tiled/layout-tiled/build/_deps/fmt-src/include/fmt/base.h:1683:5: note: 5 uninitialized fields at the end of the constructor call
1683 | ignore_unused(arg_index, named_arg_index);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
||
| hsum = _mm_hadd_pd(hsum, hsum); | ||
| acc = _mm_cvtsd_f64(hsum); | ||
| } else { | ||
| for (size_t k = 0; k < std::min(TO, O - z); ++k) { | ||
| acc += tA[i, k] * tB[k, j]; | ||
| } | ||
| } | ||
| tC[i, j] += acc; | ||
| #else | ||
| #if defined(LAYOUT_TILED_BENCH_ENABLE_OMP) | ||
| #pragma omp simd | ||
| #endif | ||
| for (size_t k = 0; k < std::min(TO, O - z); ++k) { | ||
| acc += tA[i, k] * tB[k, j]; | ||
| } | ||
| tC[i, j] += acc; | ||
| #endif | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #if defined(__AVX__) && defined(__FMA__) | ||
| template <size_t TM, size_t TN, size_t TO, class MdSpanA, class MdSpanB, class MdSpanC> | ||
| requires IsMdSpan2D<MdSpanA> && IsMdSpan2D<MdSpanB> && IsMdSpan2D<MdSpanC> | ||
| auto manual_blocked_tiled_subview_vec_unroll2(const MdSpanA A, const MdSpanB B, MdSpanC C) -> void { | ||
| assert(A.extent(1) == B.extent(0)); | ||
| assert(A.extent(0) == C.extent(0)); | ||
| assert(B.extent(1) == C.extent(1)); | ||
|
|
||
| using ATile = std::mdspan<Type, std::extents<size_t, TM, TO>, typename MdSpanA::mapping_type::inner_layout_type>; | ||
| using BTile = std::mdspan<Type, std::extents<size_t, TO, TN>, typename MdSpanB::mapping_type::inner_layout_type>; | ||
| using CTile = std::mdspan<Type, std::extents<size_t, TM, TN>, typename MdSpanC::mapping_type::inner_layout_type>; | ||
|
|
||
| constexpr size_t MR = 4; | ||
| constexpr size_t NR = 8; | ||
|
|
||
| const size_t M = A.extent(0); | ||
| const size_t N = B.extent(1); | ||
| const size_t O = A.extent(1); | ||
|
|
||
| #if defined(LAYOUT_TILED_BENCH_ENABLE_OMP) | ||
| #pragma omp parallel for schedule(static) | ||
| #endif | ||
| for (size_t x = 0; x < M; x += TM) { | ||
| for (size_t y = 0; y < N; y += TN) { | ||
| auto tC = CTile(C.data_handle() + C.mapping().tile_offset_outer(x, y)); | ||
| for (size_t z = 0; z < O; z += TO) { | ||
| const auto tA = ATile(A.data_handle() + A.mapping().tile_offset_outer(x, z)); | ||
| const auto tB = BTile(B.data_handle() + B.mapping().tile_offset_outer(z, y)); | ||
| // 4x8 register blocking | ||
| for (size_t i = 0; i < TM; i += MR) { | ||
| for (size_t j = 0; j < TN; j += NR) { | ||
| // 8 accumulators over 4 rows/2 columns | ||
| __m256d vc00 = _mm256_loadu_pd(&tC[i, j]); | ||
| __m256d vc01 = _mm256_loadu_pd(&tC[i, j + 4]); | ||
| __m256d vc10 = _mm256_loadu_pd(&tC[i + 1, j]); | ||
| __m256d vc11 = _mm256_loadu_pd(&tC[i + 1, j + 4]); | ||
| __m256d vc20 = _mm256_loadu_pd(&tC[i + 2, j]); | ||
| __m256d vc21 = _mm256_loadu_pd(&tC[i + 2, j + 4]); | ||
| __m256d vc30 = _mm256_loadu_pd(&tC[i + 3, j]); | ||
| __m256d vc31 = _mm256_loadu_pd(&tC[i + 3, j + 4]); | ||
|
|
||
| for (size_t k = 0; k < TO; ++k) { | ||
| // Broadcast 4 elements of A | ||
| __m256d sa0 = _mm256_broadcast_sd(&tA[i, k]); | ||
| __m256d sa1 = _mm256_broadcast_sd(&tA[i + 1, k]); | ||
| __m256d sa2 = _mm256_broadcast_sd(&tA[i + 2, k]); | ||
| __m256d sa3 = _mm256_broadcast_sd(&tA[i + 3, k]); | ||
|
|
||
| // Multiply broadcast A with 2 columns of B | ||
| __m256d vb0 = _mm256_loadu_pd(&tB[k, j]); | ||
| vc00 = _mm256_fmadd_pd(sa0, vb0, vc00); | ||
| vc10 = _mm256_fmadd_pd(sa1, vb0, vc10); | ||
| vc20 = _mm256_fmadd_pd(sa2, vb0, vc20); | ||
| vc30 = _mm256_fmadd_pd(sa3, vb0, vc30); | ||
| __m256d vb1 = _mm256_loadu_pd(&tB[k, j + 4]); | ||
| vc01 = _mm256_fmadd_pd(sa0, vb1, vc01); | ||
| vc11 = _mm256_fmadd_pd(sa1, vb1, vc11); | ||
| vc21 = _mm256_fmadd_pd(sa2, vb1, vc21); | ||
| vc31 = _mm256_fmadd_pd(sa3, vb1, vc31); | ||
| } | ||
|
|
||
| // Store accumulators | ||
| _mm256_storeu_pd(&tC[i, j], vc00); | ||
| _mm256_storeu_pd(&tC[i, j + 4], vc01); | ||
| _mm256_storeu_pd(&tC[i + 1, j], vc10); | ||
| _mm256_storeu_pd(&tC[i + 1, j + 4], vc11); | ||
| _mm256_storeu_pd(&tC[i + 2, j], vc20); | ||
| _mm256_storeu_pd(&tC[i + 2, j + 4], vc21); | ||
| _mm256_storeu_pd(&tC[i + 3, j], vc30); | ||
| _mm256_storeu_pd(&tC[i + 3, j + 4], vc31); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| #else | ||
| #error unsupported impl on target arch | ||
| #endif | ||
|
|
||
| #if defined(LAYOUT_TILED_BENCH_ENABLE_BLAS) | ||
| template <class MdSpanA, class MdSpanB, class MdSpanC> | ||
| requires IsMdSpan2D<MdSpanA> && IsMdSpan2D<MdSpanB> && IsMdSpan2D<MdSpanC> && IsLayoutRight<MdSpanA> | ||
|
|
@@ -339,10 +467,10 @@ | |
| auto mtlB_l2 = std::mdspan<Type, Exts, layout_tiled::layout_ltl<TILE_L2, TILE_L2>>(tlB_l2.get(), cfg.n_, cfg.n_); | ||
|
|
||
| // Matrix initializations | ||
| ankerl::nanobench::Rng rng_naive(42); | ||
| benchmark_utils::fill_2d_uniform01(mrA, rng_naive); | ||
| benchmark_utils::fill_2d_uniform01(mrB, rng_naive); | ||
| benchmark_utils::fill_2d_uniform01(mlB, rng_naive); | ||
| ankerl::nanobench::Rng rng(42); | ||
| benchmark_utils::fill_2d_uniform01(mrA, rng); | ||
| benchmark_utils::fill_2d_uniform01(mrB, rng); | ||
| benchmark_utils::fill_2d_copy(mlB, mrB); | ||
| benchmark_utils::fill_2d_copy(mtrA_l1, mrA); | ||
| benchmark_utils::fill_2d_copy(mtrB_l1, mrB); | ||
| benchmark_utils::fill_2d_copy(mtlB_l1, mlB); | ||
|
|
@@ -388,6 +516,27 @@ | |
| res = benchmark_utils::check(ref, benchmark_utils::l2_norm(mtrC_l2), "RTR-L2_mbtsv", big_o); | ||
| benchmark_utils::fill_zeroes(mtrC_l2); | ||
|
|
||
| manual_blocked_tiled_subview<TILE_L1, TILE_L1, TILE_L1>(mtrA_l1, mtlB_l1, mtrC_l1); | ||
| res = benchmark_utils::check(ref, benchmark_utils::l2_norm(mtrC_l1), "RTR-BLTL-L1_mbtsv", big_o); | ||
| benchmark_utils::fill_zeroes(mtrC_l1); | ||
| manual_blocked_tiled_subview<TILE_L2, TILE_L2, TILE_L2>(mtrA_l2, mtlB_l2, mtrC_l2); | ||
| res = benchmark_utils::check(ref, benchmark_utils::l2_norm(mtrC_l2), "RTR-BLTL-L2_mbtsv", big_o); | ||
| benchmark_utils::fill_zeroes(mtrC_l2); | ||
|
|
||
| manual_blocked_tiled_subview_vec_unroll_k<TILE_L1, TILE_L1, TILE_L1>(mtrA_l1, mtlB_l1, mtrC_l1); | ||
| res = benchmark_utils::check(ref, benchmark_utils::l2_norm(mtrC_l1), "RTR-BLTL-L1_mbtvu", big_o); | ||
| benchmark_utils::fill_zeroes(mtrC_l1); | ||
| manual_blocked_tiled_subview_vec_unroll_k<TILE_L2, TILE_L2, TILE_L2>(mtrA_l2, mtlB_l2, mtrC_l2); | ||
| res = benchmark_utils::check(ref, benchmark_utils::l2_norm(mtrC_l2), "RTR-BLTL-L2_mbtvu", big_o); | ||
| benchmark_utils::fill_zeroes(mtrC_l2); | ||
|
|
||
| manual_blocked_tiled_subview_vec_unroll2<TILE_L1, TILE_L1, TILE_L1>(mtrA_l1, mtrB_l1, mtrC_l1); | ||
| res = benchmark_utils::check(ref, benchmark_utils::l2_norm(mtrC_l1), "RTR-BLTL-L1_mbt_vu2", big_o); | ||
| benchmark_utils::fill_zeroes(mtrC_l1); | ||
| manual_blocked_tiled_subview_vec_unroll2<TILE_L2, TILE_L2, TILE_L2>(mtrA_l2, mtrB_l2, mtrC_l2); | ||
| res = benchmark_utils::check(ref, benchmark_utils::l2_norm(mtrC_l2), "RTR-BLTL-L2_mbt_vu2", big_o); | ||
| benchmark_utils::fill_zeroes(mtrC_l2); | ||
|
|
||
| return res; | ||
| #else | ||
| // Benchmarks | ||
|
|
@@ -438,10 +587,22 @@ | |
| manual_blocked_tiled_subview<TILE_L2, TILE_L2, TILE_L2>(mtrA_l2, mtrB_l2, mtrC_l2); | ||
| }); | ||
| bs.run(fmt::format("RTR_BLTL-{0}x{0} mbtsv", TILE_L1), [&]() { | ||
| manual_blocked_tiled_subview_BLTL<TILE_L1, TILE_L1, TILE_L1>(mtrA_l1, mtlB_l1, mtrC_l1); | ||
| manual_blocked_tiled_subview<TILE_L1, TILE_L1, TILE_L1>(mtrA_l1, mtlB_l1, mtrC_l1); | ||
| }); | ||
| bs.run(fmt::format("RTR_BLTL-{0}x{0} mbtsv", TILE_L2), [&]() { | ||
| manual_blocked_tiled_subview_BLTL<TILE_L2, TILE_L2, TILE_L2>(mtrA_l2, mtlB_l2, mtrC_l2); | ||
| manual_blocked_tiled_subview<TILE_L2, TILE_L2, TILE_L2>(mtrA_l2, mtlB_l2, mtrC_l2); | ||
| }); | ||
| bs.run(fmt::format("RTR_BLTL-{0}x{0} mbt_vu", TILE_L1), [&]() { | ||
| manual_blocked_tiled_subview_vec_unroll_k<TILE_L1, TILE_L1, TILE_L1>(mtrA_l1, mtlB_l1, mtrC_l1); | ||
| }); | ||
| bs.run(fmt::format("RTR_BLTL-{0}x{0} mbt_vu", TILE_L2), [&]() { | ||
| manual_blocked_tiled_subview_vec_unroll_k<TILE_L2, TILE_L2, TILE_L2>(mtrA_l2, mtlB_l2, mtrC_l2); | ||
| }); | ||
| bs.run(fmt::format("RTR_BLTL-{0}x{0} mbt_vu2", TILE_L1), [&]() { | ||
| manual_blocked_tiled_subview_vec_unroll2<TILE_L1, TILE_L1, TILE_L1>(mtrA_l1, mtrB_l1, mtrC_l1); | ||
| }); | ||
| bs.run(fmt::format("RTR_BLTL-{0}x{0} mbt_vu2", TILE_L2), [&]() { | ||
| manual_blocked_tiled_subview_vec_unroll2<TILE_L2, TILE_L2, TILE_L2>(mtrA_l2, mtrB_l2, mtrC_l2); | ||
| }); | ||
|
|
||
| // Output | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clang-tidy diagnostic
benchmarks/gemm.cpp:294:30: warning: [portability-simd-intrinsics]