# AVX / NEON Intrinsic Functions: When Should They Be Used?

Théo Boivin, Jeffrey Legaux

<figure><img src="image_1.png" /></figure>

To cite this version:

Théo Boivin, Jeffrey Legaux. AVX / NEON Intrinsic Functions: When Should They Be Used?. 2026. hal-05447052

HAL Id: hal-05447052

https://hal.science/hal-05447052v1

Preprint submitted on 7 Jan 2026

HAL is a multi-disciplinary open access
archive for the deposit and dissemination of sci-
entific research documents, whether they are pub-
lished or not. The documents may come from
teaching and research institutions in France or
abroad, or from public or private research centers.

L'archive ouverte pluridisciplinaire HAL, est
destinée au dépôt et à la diffusion de documents
scientifiques de niveau recherche, publiés ou non,
émanant des établissements d'enseignement et de
recherche français ou étrangers, des laboratoires
publics ou privés.

<figure><img src="image_2.png" /></figure>

Distributed under a Creative Commons CC BY 4.0 - Attribution - International License
# AVX / NEON Intrinsic Functions:
When Should They Be Used? *

Théo Boivin† and Joeffrey Legaux‡

CERFACS, 31057 Toulouse Cedex 1, France

## Abstract

A cross-configuration benchmark is proposed to explore the capacities and limitations of AVX / NEON intrinsic functions in a generic context of development project, when a vectorisation strategy is required to optimise the code. The main aim is to guide developers to choose when using intrinsic functions, depending on the OS, architecture and/or available compiler. Intrinsic functions were observed highly efficient in conditional branching, with intrinsic version execution time reaching around 5% of plain code execution time. However, intrinsic functions were observed as unnecessary in many cases, as the compilers already well auto-vectorise the code.

<figure><img src="image_3.png" /></figure>

<footnote>*CC BY Distributed under a CC-BY 4.0 licence.</footnote>

<footnote>†Email: theo.boivin@email.fr</footnote>

<footnote>‡Email: joeffrey.legaux@cerfacs.fr</footnote>
# 1 Introduction

When it comes to programming, optimisation is at the core of computation efficiency. If the computation speed need is real, which is a fundamental prerequisite to talk about optimisation, a possible technique is the SIMD (Single Instruction, Multiple Data) that consists in applying a single instruction to multiple data simultaneously, on a single hardware unit. In this case, the data is said “vectorised”.

The CPU (Central Processing Unit), and more precisely a core of a CPU on current common architectures such as x86-64 or ARM, is commonly seen as a scalar device: its instructions only process a single piece of data at a time. However, this description is not exact. The instructions are applied on a register, a memory of limited capacity going from 64 to 512 bits depending on the chip. The manufacturers worked to extend the usage of these registers, introducing in the late 1990s the first SIMD instruction sets to the x86 architecture instruction set, first the MMX introduced by Intel in 1997, then *3DNow!* proposed by AMD in 1998, then the SSE (Streaming SIMD Extensions) introduced by Intel in 1999. The SSE instruction set originally used eight 128-bits registers called XMM0-XMM7. This instruction set made possible the vectorisation of floating operations, more precisely four 32-bits single-precision floating-points or two 64-bits double-precision floating-points. At the second generation of Intel® Core™ processor family (nicknamed “Sandy Bridge”) in 2011, Intel introduced sixteen 256-bit registers called YMM0-YMM15 and the AVX (Advanced Vector Extensions) instruction set, leading to the doubling of registers memory. This memory was doubled once again in 2013 through the introduction of thirty-two 512-bits registers called ZMM0-ZMM31 within the Intel® Xeon Phi™ processor (nicknamed “Knights Landing”), as well as the AVX-512 instruction set that introduced numerous features (masking, enhanced mathematics, better standard language compliance) [1–5].

Nowadays, the intrinsic functions (called “intrinsics” in this paper) belongs to the toolbox of developers to explicitly manipulate vector instruction sets and improve the vectorisation of computation, mostly in C++. Many researches demonstrate the enhanced performances using intrinsics. For example, Jeong *et al.* (2012) [6] compared the plain, SSE4.2, AVX1 intrinsic versions of a simple addition loop, where the term “plain” refers to the C++ standard version of a code, without any use of intrinsic. They measured clear improvement by adjusting the data reuse, decreasing the required number of CPU clocks by around 11 with AVX version. Jinchen *et al.* (2012) [7] leveraged data reuse to optimise mathematical functions (cos, sin, sqrt, etc.), obtaining an average improvement of around 8%. Hassan *et al.* (2016, 2018) [8, 9] addressed the algorithm of large matrices multiplication. They detected a better performance of Intel compiler compared to MSVC++ and an improvement with their proposed algorithm between 14% and 18%. Shabanov *et al.* (2019) [10] studied the application of AVX-512 intrisics in conditional programming. As an important note, they advised to remove the unlikely conditional branches from the main context, on one hand to facilitate vectorisation, and on the other hand to avoid unnecessary vectorised computation. Cebrian *et al.* (2020) [11] mostly focused on energy consumption by compar-
ing thread-level parallelisation and SIMD. They noticed that even though both approaches
were comparable in terms of computation speed, SIMD consumed much less power than
threading. Fortin et al. (2021) [12] optimised the algorithms of polynomial factorisation
and polynomial greatest common divisor with two versions, respectively using AVX2 and
AVX-512 intrinsics. The first version gets a speedup factor of 3.7 and the second one a
speedup factor of 7.2. In addition to these raw performance studies, vectorisation using
AVX intrisics was used to improve the performance of many applications, such as fluid
vibration problems [13], heart simulation [14], N-body problem [15], seismic wave propa-
gation [16] or particle swarm simulation [17].

The current literature consequently underlines the efficiency of optimisation using AVX
intrisics. The quick review presented here focused on AVX instruction set, adapted for
x86-64 architecture, but the equivalent instruction set for ARM architecture, NEON, can
be joined to the following observation: using intrisics, whether AVX or NEON, seems
quite underestimated in general development practice. The original question at the origin of
this paper mostly raised from this general under-use: the optimisation of CPU vectorisation
through programming is not particularly common. Consequently, for a developer discover-
ing AVX / NEON intrisics capabilities, the task is hard to answer a simple question: For
my project, are AVX / NEON intrisics interesting? Indeed, most of cited references are
mostly punctual in terms of configuration (OS, CPU architecture, compiler). In this sense,
Table 1 lists all the configurations of all cited references. The majority of experiments were
carried out on x86-64 architecture, using ICC (Intel C++ Compiler) or GCC (GNU Com-
piler Collection) compilers. Some, however, made a comparison between two compilers
[8, 9, 12, 14, 16]. The work presented in these papers remain quite impressive and com-
plete, but a lack exists concerning the wider guidelines perspective, essential to orientate
developers to use or not AVX / NEON intrisics in their project. The question of “When
intrisics should be used?” is legitimate, because an aspect that few references underline
is their cost in terms of readability: intrisics are tedious. Readability and maintainabil-
ity in a development project are as important as efficiency, so intrisics should be chosen
wisely. Furthermore, the implementation of intrisics is not systematically efficient. For
example, Gottschlag et al. (2020a and 2020b) [18, 19] questioned the efficiency of AVX
instructions. They studied the CPU frequency reduction generated by the processor during
AVX-512 intrisics to avoid over-consumption. The delay of frequency increase after the
AVX-512 code execution may overlap on plain code execution, which reduces the efficiency
of the latter. This leads to a balance between AVX-512 code speedup and surrounding plain
code speedown, only detectable within the main program. This constitutes a supplementary
reason to think intrisics carefully, in order to use them optimally.
<table><thead><tr><th>Reference</th><th>OS / Kernel</th><th>Architecture</th><th>Compiler</th></tr></thead><tbody><tr><td>Gepner et al. [1]</td><td>RedHat Enterprise Linux 6, kernel 2.6.3271.el6.x86_64</td><td>x86-64</td><td>ICC v12.0</td></tr><tr><td>Anderson et al. [2]</td><td>–</td><td>x86-64</td><td>–</td></tr><tr><td>Mileff et al. [3]</td><td>Linux</td><td>x86-64</td><td>GCC v11.1</td></tr><tr><td>Jeong et al. [6]</td><td>Fedora 17, kernel 3.5.2-3.fc17</td><td>x86-64</td><td>GCC v4.7</td></tr><tr><td>Jinchen et al. [7]</td><td>–</td><td>–</td><td>–</td></tr><tr><td>Hassan et al. [8]</td><td>Windows 10</td><td>x86-64</td><td>ICC / MSVC++ 2015 v140</td></tr><tr><td>Hassan et al. [9]</td><td>Windows 10</td><td>x86-64</td><td>ICC v17.0 / MSVC++ 2015</td></tr><tr><td>Shabanov et al. [10]</td><td>–</td><td>x86-64</td><td>–</td></tr><tr><td>Cebrian et al. [11]</td><td>Ubuntu 18.04, kernel 4.15</td><td>x86-64</td><td>GCC v7.3</td></tr><tr><td>Fortin et al. [12]</td><td>–</td><td>x86-64</td><td>GCC v8.2 / ICC v19.0</td></tr><tr><td>Francés et al. [13]</td><td>–</td><td>x86-64</td><td>GCC †</td></tr><tr><td>Jarvis et al. [14]</td><td>–</td><td>x86-64</td><td>GCC v8.2 / ICC v18.0</td></tr><tr><td>Pedregosa-Gutierrez et al. [15]</td><td>–</td><td>x86-64</td><td>ICC †</td></tr><tr><td>Jubertie et al. [16]</td><td>–</td><td>ARM</td><td>Armclang v20.0 / GCC v10.0</td></tr><tr><td>Safarik & Snasel [17]</td><td>Windows 10</td><td>x86-64</td><td>–</td></tr></tbody></table>

<caption>**Table 1:** Review of cited references configurations — An empty cell means that no data was provided / found in the reference. **GCC**: GNU Compiler Collection, **ICC**: Intel C++ Compiler, **MSVC++**: Microsoft Visual C++.</caption>

<footnote>† Not clearly explicated in reference, deduced from content.</footnote>
The aim of this paper is to propose a cross-configuration benchmark, based on simple generic tests, in order to estimate the configurations where AVX / NEON intrinsics are susceptible to notably improve performance. An important assumption concerning the development project is stated here: vectorisation is the chosen strategy. The aim of this paper is not to evaluate the efficiency of vectorised code compared to non-vectorised code, but instead, to compare auto-vectorised code and manually vectorised code. Even if this paper cannot pretend to be exhaustive for all development situations, the main idea is to provide a wider perspective on AVX / NEON intrinsics capabilities and limitations, in order to draw a clearer routine of choice for intrinsics use.

2 Experiment

2.1 Scenarios

The experiment consisted in several sample programs commonly met in development prac-
tice. When it comes to SIMD, the vectorised program must present a loop-like form, i.e.
implments a common instruction on multiple data. Thus, each sample program was a loop
of operations made on standard vectors. Several scenarios were approached, starting from
basic operations (addition, multiplication) to more and more complex cases, using index
offset, advanced operations (cos, sqrt, pow, etc.), conditions on index and conditions on
random data. In total, eight scenarios were benchmarked, detailed in Listing 1 with descrip-
tions and plain codes. Most of the additional complexity† implemented at each scenario
(for example the index offset or the conditions) were chosen to test the auto-vectorisation
of compilers. The intrinsic implementation was made using “SIMD Everywhere” [20], an
open-source header-only library that provides fast and portable intrinsic implementations,
for both AVX and NEON instruction sets. In order to ensure a fully portable code between
devices, only 256-bit registers instructions were used (simde__m256 type). The implementa-
tion strategy was based on the load / store technique: loading data in intermediary variables
through simde_mm256_loadu_ps() method, computing, then storing back in standard vectors
using simde_mm256_storeu_ps(). For advanced operations (cos, sqrt, pow, etc.), the intrinsic
functions were used (simde_mm256_cos_ps(), simde_mm256, pow ps(),
etc.). As a set of examples, Listing 2 details the intrinsic versions of scenarios 1 and 6. Vec-
tors A, B, C and D are written with the same names in both Listing 1 and Listing 2 for clarity,
but they were distinguished in the benchmark to avoid the influence of memory cache.

<footnote>† The term “complexity” refers to the problem complexity, not the computational algorithm complexity.</footnote>
// Scenario 1
// Basic operations.
for (size_t i = 0; i < Niter; ++i) {
    D[i] = A[i] * B[i] + C[i];
}

// Scenario 3
// Advanced operations.
for (size_t i = 0; i < Niter; ++i) {
    D[i] = A[i] * std::sqrt(B[i])
        + std::abs(C[i])
        - std::cos(A[i]) / C[i]
        + std::pow(B[i], 2.5f);
}

// Scenario 5
// Simple condition on index
// with basic operations.
for (size_t i = 0; i < Niter; ++i) {
    if (i % 2 == 0)
        C[i] = A[i] + B[i];
    else
        C[i] = A[i] - B[i];
}

// Scenario 7
// Simple condition on random data
// with sub-branches and basic
// operations.
for (size_t i = 0; i < Niter; ++i) {
    if (A[i] > 5) {
        if (B[i] >= 8)
            C[i] = A[i] * B[i];
        else if (B[i] <= 5)
            C[i] = A[i] / B[i];
        else
            C[i] = A[i] + B[i];
    }
    else
        C[i] = A[i] - B[i];
}

// Scenario 2
// Basic operations with index offset.
for (size_t i = 1; i < Niter-1; ++i) {
    D[i] = A[i-1] * B[i] + C[i] + B[i+1];
}
D[0] = 0.0f;
D[Niter-1] = 0.0f;

// Scenario 4
// Advanced operations with index
// offset.
for (size_t i = 1; i < Niter-1; ++i) {
    D[i] = A[i-1] * std::sqrt(B[i])
        + std::abs(C[i])
        - std::cos(A[i]) / C[i]
        + std::pow(B[i+1], 2.5f);
}
D[0] = 0.0f;
D[Niter-1] = 0.0f;

// Scenario 6
// Simple condition on random data
// with basic operations.

for (size_t i = 0; i < Niter; ++i) {
    if (A[i] > 5)
        C[i] = A[i] + B[i];
    else
        C[i] = A[i] - B[i];
}

// Scenario 8
// Simple condition on random data
// with sub-branches and advanced
// operations.
for (size_t i = 0; i < Niter; ++i) {
    if (A[i] > 5) {
        if (B[i] >= 8)
            C[i] = std::sqrt(A[i]);
        else if (B[i] <= 5)
            C[i] = std::pow(A[i], B[i]);
        else
            C[i] = std::cos(A[i]);
    }
    else
        C[i] = std::ceil(A[i]);
}

<caption>**Listing 1:** Scenarios plain codes — Each scenario consists of a loop over Niter = 5e7, on std::vector `<float>` A, B, C, D. For scenario 6, vector A was filled with random floats between 1 and 10. For scenarios 7 and 8, both vectors A and B were filled with random floats between 1 and 10.</caption>
```c
// Scenario 1
simde_m256 a;
simde_m256 b;
simde_m256 c;
simde_m256 d;
for (size_t i = 0; i <= Niter - AVX_PSVEC_SIZE; i += AVX_PSVEC_SIZE) {
    a = simde_mm256_loadu_ps(&A[i]);
    b = simde_mm256_loadu_ps(&B[i]);
    c = simde_mm256_loadu_ps(&C[i]);
    d = simde_mm256_multn_ps(a, b);
    d = simde_mm256_add_ps(d, c);
    simde_mm256_storeu_ps(&D[i], d);
}
for (size_t i = Niter - Niter % AVX_PSVEC_SIZE; i < Niter; ++i) {
    D[i] = A[i] * B[i] + C[i];
}
```

```c
// Scenario 6
simde_m256 a;
simde_m256 b;
simde_m256 c;
simde_m256 mask;
simde_m256 val_f_5 = simde_mm256_set1_ps(5.0f);
for (size_t i = 0; i <= Niter - AVX_PSVEC_SIZE; i += AVX_PSVEC_SIZE) {
    a = simde_mm256_loadu_ps(&A[i]);
    b = simde_mm256_loadu_ps(&B[i]);
    mask = simde_mm256_cmp_ps(a, val_f_5, SIMDE_CMP_GT_OS);
    c = simde_mm256.blendv_ps(
        simde_mm256_sub_ps(a, b),
        simde_mm256_add_ps(a, b),
        mask
    );
    simde_mm256_storeu_ps(&C[i], c);
}
for (size_t i = Niter - Niter % AVX_PSVEC_SIZE; i < Niter; ++i) {
    if (A[i] > 5)
        C[i] = A[i] + B[i];
    else
        C[i] = A[i] - B[i];
}
```

<caption>**Listing 2:** Scenarios intrinsic codes (1 and 6) — Each scenario consists of a loop over Niter - AVX_PSVEC_SIZE, with step AVX_PSVEC_SIZE, on std::vector `<float>` A, B, C, D. We have Niter = 5e7, AVX_PSVEC_SIZE the vector size with floats contained in 256-bit register:uner constexpr size_t AVX_PSVEC_SIZE = 256 / (8 * sizeof (float)). Vectors A, B, C, D did not share the same memory as vectors A, B, C, D in Listing 1.</caption>
## 2.2 Configurations

Three different devices were used, one for each main operating system (Linux, macOS and Windows). For each operating system, one or more different compilers were tested among Clang, GCC, ICC and MSVC++. The compiler GCC has been chosen as the reference one (tested for all operating systems), using a common version (14.3.0). In total, six configurations were tested: GCC on Linux, Clang and GCC on macOS, GCC, ICC and MSVC++ on Windows, respectively named *lin_gcc*, *mac_clang*, *mac_gcc*, *win_gcc*, *win_intel* and *win_msvc*. For further details, Table 2 details the hardware / software configurations. For each compiler, the level of optimisation was also addressed by testing the most common levels (O0, O1, O2 and O3). For ICC and MSVC++ compilers, the equivalent level for O0 was O0. For MSVC++ compiler, the level O3 was not tested as, the maximum is O2.

<table><thead><tr><th>Name</th><th>Processor</th><th>Architecture</th><th>Cores</th><th>Memory</th></tr></thead><tbody><tr><td>lin_##</td><td>Intel® Xeon® Gold 6140<br>CPU @ 2.30GHz</td><td>x86-64</td><td>18</td><td>16 GB</td></tr><tr><td>mac_##</td><td>Apple M3</td><td>ARM64</td><td>8</td><td>16 GB</td></tr><tr><td>win_##</td><td>Intel® Core™ i5-7400<br>CPU @ 3.00GHz</td><td>x86-64</td><td>4</td><td>24 GB</td></tr></tbody></table>

<table><thead><tr><th>Name</th><th>OS / Kernel</th></tr></thead><tbody><tr><td>lin_##</td><td>Linux, kernel 4.18.0-553.el8_10.x86_64</td></tr><tr><td>mac_##</td><td>macOS Sequoia 15.6.1</td></tr><tr><td>win_##</td><td>Windows 10 Professional 22H2 19045.6282</td></tr></tbody></table>

<table><caption>**Table 2:** Configurations of benchmark — Six configurations were tested: *lin_gcc*, *mac_clang*, *mac_gcc*, *win_gcc*, *win_intel*, *win_msvc*.</caption><thead><tr><th>Name</th><th>Compiler</th></tr></thead><tbody><tr><td>##_clang</td><td>Clang 21.1.0</td></tr><tr><td>##_gcc</td><td>GCC 14.3.0</td></tr><tr><td>##_intel</td><td>Intel 2025.2 (ICC)</td></tr><tr><td>##_msvc</td><td>MSVC 2022 Community Version 19.44.35215 for x64</td></tr></tbody></table>
## 2.3 Benchmark

The benchmark consisted in a home-made framework based on a unique main file, in which all scenarios were built, but only one was selected and run. For each scenario, to ensure the absence of device speed reduction due to an unforeseen background process, the plain and intrinsic versions were alternately run with the following scheme [[plain,intrinsic], [plain,intrinsic], ...] with size 50. Each time, their results were checked to be equal and their respective execution times were measured. As a matter of precaution, the scheme [[plain,plain,...], [intrinsic,intrinsic,...]] with sub-sizes 50 were also tested, but similar execution times were obtained. The resulting execution times were reduced to a pair of mean / standard deviation, one for each code version. The execution time improvement / degradation was evaluated through the execution time ratio of intrinsic version over plain version, defined by:

$$ \tau = \frac{\Delta T_I}{\Delta T_P} \qquad (1) $$

where $\Delta T_I$ and $\Delta T_P$ are respectively the intrinsic and the plain execution times. It gives a positive value, where a value below one (or 100%) expresses an execution time reduction of intrinsic version, and reciprocally for a value above one (or 100%). In our particular case, the lower the value of $\tau$, the better. The standard deviation of the execution time ratio was computed through the equation:

$$ \sigma(\tau) = \left| \frac{\Delta T_I}{\Delta T_P} \right| \sqrt{ \left( \frac{\sigma(\Delta T_I)}{\Delta T_I} \right)^2 + \left( \frac{\sigma(\Delta T_P)}{\Delta T_P} \right)^2 } \qquad (2) $$

where $\sigma(y)$ represents the standard deviation of variable y, and variables $\Delta T_I$ and $\Delta T_P$ were supposed uncorrelated.

# 3 Results

## 3.1 Execution time ratio

As a first set of results, Fig. 1 displays the execution time ratio as a function of configurations and optimisation level, for each scenario. All graphs show the range going from 0% to 200%. A first observation is the drastic execution time ratio that appears on macOS system for optimisation level O0, systematically for Clang compiler and depending on scenario for GCC compiler. On these configurations and with deactivated optimisation, the use of intrinsics was clearly destructive. For example, for optimisation level O0 and mac_clang, the execution time ratio varies between 320% and 910% depending on the scenario. Another interesting point appears in the four first scenarios (top graphs in Fig. 1, from 1 to 4), where scenarios pairs 1/2 and 3/4 are respectively quite similar. Consequently, the situation with index offset seems equivalently managed by compilers, whatever the configuration. For basic operations (graphs 1 and 2 in Fig. 1), the use of intrinsics leads to promising execution
time ratio when the optimisation level is set to O1 (square markers), reaching 29.76±3.8% for *mac_gcc* configuration in scenario 1. However, the interest is much less clear when optimisation level is set to most aggressive (O2 for MSVC++ and O3 for other compilers). Indeed, the execution time ratio is much closer to 100%, which means that no change was brought by intrinsic version. The trend even reverses for *mac_gcc* configuration, where the execution time ratio is around 148.8±12.9% in scenario 2. This observation is similar for advanced operations (graphs 3 and 4 in Fig. 1), where the optimisation level O3 gives execution time ratio even closer to 100%. An exception occurs for *win_msvc* configuration where the optimisation level O2 generated an execution time ratio of 5.0±0.1% in scenario 3, which constitutes an impressive speedup. When it comes to conditional scenarios (bottom graphs in Fig. 1, from 5 to 8), the simple case of condition based on index (scenario 5) gives a similar trend. A promising execution time ratio appears at low optimisation level, but it is questioned when the latter is increased, especially for *mac_clang* and *mac_intel* where the execution time ratio is close to 100%. This result was expected in the sense that the conditional branches were made based on vector index, which could be predicted and auto-vectorised by compilers. For this reason, in scenarios 6 to 8, the conditions were based on random data so that compiler could not predict the branch routing in advance for each vector index, making the auto-vectorisation difficult. The consequence is clearly captured in scenario 6, where even at optimisation level O3, *mac_gcc* and *win_gcc* present impressive execution time ratios of 6.9±1.0% and 15.0±0.2% respectively. Nevertheless, unexpectedly, *lin_gcc*, *mac_clang* and *win_intel* still present execution time ratio close to 100%. This observation occurs once again in scenario 7 (with additional conditional sub-branches), except for *mac_clang* configuration that generates a non-negligible execution time ratio of 44.3±4.8% at optimisation level O3. Finally, in scenario 8 (conditions with sub-branches and advanced operations), the trend completely reverses: all configurations present noticeable execution time ratios above 100%, which signifies an important increase of execution time due to intrinsic version. The only significant configuration remains *win_msvc*, that still present really promising values, for example at optimisation level O2 where it equals 12.8±0.6%.

## 3.2 Execution time

A major conclusion of execution time ratio analysis is that, globally, the *win_msvc* is notably better performing when the intrinsic version is used. However, the execution time ratio defined by Eq. (1) introduces a major drawback: it does not reveal the order of magnitude of execution time. This is useful to make a comparison between all configurations, but it fails to answer an important question: *For a particular device, which configuration is the best?* In this perspective, Fig. 2 displays the execution times ($\Delta T_p$ and $\Delta T_i$) for the most aggressive optimisation levels (O2 for MSVC++ and O3 for other compilers). Background areas were added to distinguish each device (Linux, macOS or Windows) from one another. Thus, precisely comparing the absolute execution times from different areas is not relevant,
<figure><img src="image_4.png" /><figcaption>**Figure 1:** *Benchmarking results — Execution time ratio, based on Eq. (1), as a function of configuration and optimisation level. Standard deviation is based on Eq. (2). Each graph corresponds to a scenario (see Listing 1 for details), whose index is indicated in the top right corner.*</figcaption></figure>
as it compares different devices that certainly perform differently. Still, it is possible to
compare the trends. Scenarios with basics operations only (scenarios 1 and 2 in Fig. 2),
including conditions on vector index (scenario 5 in Fig. 2) are quite stable and cheap, plain
and intrinsic versions are clearly comparable. At the opposite, the introduction of advanced
operations (cos, sqrt, pow, etc., in scenarios 3 and 4 in Fig. 2) drastically increases the ex-
ecution time, especially for win_msvc configuration. Similarly, the conditions on random
data (scenarios 6, 7 and 8) gradually increases the execution time, especially as the program
is complicated. These observations remain predictable and do not constitute the most in-
teresting aspects of Fig. 2. The most noticeable result is the case of win_intel / win_msvc
pair. In every scenario, win_msvc sees an important speedup thanks to intrinsic version, but
it systematically makes the execution time reach the order of magnitude of win_intel. In
other words, win_msvc configuration requires the use of intrinsics to reach the performance
that win_intel already has in plain version. For scenarios 6 and 7 in Fig. 2, win_gcc be-
haves the same. For example, in scenario 6 (simple operations on random data with basic
operations), win_gcc and win_msvc respectively goes from 221.4±0.6 ms to 33.3±0.5 ms
and from 236.5±0.7 ms to 33.2±0.5 ms thanks to intrinsic version, but win_intel is already
at 33.6±0.5% with plain version. A similar pair exists on macOS device, where mac_gcc
systematically reaches back the execution time of mac_clang when the intrinsic version is
used, particularly for scenarios 6 and 7 in Fig. 2.

4 Discussion

The results of the proposed cross-configuration benchmark demonstrates key information: the interest of intrinsics is not only dependent on the program to be vectorised, but also on the configuration (i.e. OS, architecture and compiler). It is difficult to state clear correlation, particularly concerning the chip architecture. For example, in scenario 6 at optimisation level O3, both *mac_gcc* and *win_gcc* configurations behave similarly whereas they are run on two different architectures (respectively, ARM64 and x86-64). On the opposite, for the same scenario and optimisation level, *lin_gcc* and *win_gcc* behave quite differently whereas they run on the same architecture. When it comes to compilers, some trends come up from the results: GCC is poorly improved by intrinsics on Linux, Clang performs better than GCC on macOS, ICC performs better than GCC and MSVC++ on Windows and MSVC++ is highly improved by intrinsics on Windows. Another noticeable information is the instability of advanced operations (*cos*, *sqrt*, *pow*, etc.). Even if their implementation in intrinsic version leads at best to an unchanged execution time in scenarios 3 and 4, it still leads to an important execution time increase in scenario 8, which is clearly counterproductive from a development perspective. This constitutes a good example of the warning stated by Intel® Intrinsics Guide [21]. When looking at the information of a SVML (Short Vector Math Library) function: it is tagged as "SEQUENCE", which is described as "This intrinsic generates a sequence of instructions, which may perform worse than a native instruction.".
<figure><img src="image_5.png" /><figcaption>**Figure 2:** *Benchmarking results —* Execution times ($\Delta T_P$ and $\Delta T_I$) as a function of configuration, for most aggressive optimisation (O2 for MSVC++ and O3 for other compilers). Standard deviation is the sample uncertainty (fifty runs). Each graph corresponds to a scenario (see *Listing 1* for scenarios details), whose index is indicated on the top right corner. Background areas distinguish each device (Linux, macOS or Windows) from one another.</figcaption></figure>
Thus, the advanced operations must be addressed carefully with intrinsics. This fact is already well addressed in literature, where efforts are deployed to propose approximation functions of these operations by using only fundamental operations (addition, multiplication, subtraction, division) [22–24].

These results are interesting in the fact that they question the manner of thinking programming, especially for compiled language such as C++: beyond the implementation itself, the choice of the compiler is decisive. In other words, before thinking about CPU vectorisation programming, it is wiser to check if the right compiler and right optimisation options are used to enhance the performance. The implementation of intrisics is expensive in terms of code tediousness, so it must be considered parsimoniously, as a last resort. As a recall, the aim of this paper was to provide a routine helping in the choice of intrisics use. To fulfil this objective, Fig. 3 proposes a flow chart of generic routine to choose the right compiler and intrisics use.

<figure><img src="image_6.png" /><figcaption>**Figure 3:** Flowchart for choice of intrisics use, deduced from benchmark results. The use of intrisics is supposed available in the development context (in terms of readability and maintainability). The objective of such a routine is to optimise final performance, so by setting the optimisation level to most aggressive (O2 for MSVC++ and O3 for other compilers).</figcaption></figure>
As a matter of critic of the proposed routine, it is hard to estimate to what extent the proposed benchmark in not exhaustive enough. With the grand variety of OS and chips, numerous behaviours may have not been captured (AMD chips, Intel chips on macOS, ICC compiler on Linux, etc.), embodied by the “?” cases in Fig. 3. It is also clear that the out-of-context measurement, on single pieces of program, does not necessarily reflect the performance reality of the main program that contains them, as discussed by Gottschlag et al. (2020a and 2020b) [18, 19]. As a result, the proposed routine in Fig. 3 remains a guide and does not prevent the second piece of advice of Intel® Intrinsics Guide [21]: “*Consider the performance impact of this intrinsic.*”. The approach of this paper was not to question, but on the opposite to emphasise, what constitutes to the authors’ mind both first main rules of optimisation:

1. DOINN rule (Don't Optimise If Not Necessary).

2. MAAC rule (Measure At Any Cost).

The DOINN rule is fundamental to keep in mind that optimisation comes after a technical need, not the reverse. The MAAC rule is important to prevent technical bias to implement destructive solution: the systematic measurement of the proposed optimisation solution (through execution time, CPU clocks, etc.) is the essential way to state a robust conclusion about its real pertinence.

# 5 Conclusion

A cross-configuration benchmark was proposed to estimate the configurations where AVX / NEON intrinsic functions were susceptible to notably improve performance of a program. It was composed of eight scenarios commonly met in development practices and eligible for SIMD vectorisation. Several compilers / OS / architectures were tested: GCC on Linux (x86-64), Clang and GCC on macOS (ARM64), GCC, ICC and MSVC++ on Windows (x86-64).

Impressive execution time reduction was captured in some cases thank to intrinsic version (down to 5% of the plain version execution time), for MSVC++ with advanced operations (cos, sqrt, pow, etc.) or cases of conditional branches. However, the efficiency of intrinsic versions was not systematic, even leading to important execution time increase when advanced operations were used with conditional branches. Some trends were captured depending on compilers and OS, revealing that GCC is poorly improved by intrinsics on Linux (x86-64), Clang is more efficient than GCC on macOS (ARM64), ICC is more efficient than GCC and MSVC++ on Windows (x86-64), and MSVC++ is highly improved by intrinsics. In addition, a routine was proposed to guide the developer to choose the use or not of intrinsics, depending on the configuration constraints.

This paper mostly encourages, before the use of intrinsic functions, to explore all the most accessible optimisation techniques, starting with the choice of the right compiler and the right optimisation options.
## Acknowledgments

The authors acknowledge Isabelle d'Ast for her precious feedbacks and help in making the benchmark harmonised between operating systems. They also acknowledge the daily support of CERFACS teams through their enriching discussions.

## Credit authorship contribution statement

T.B. built and run the benchmark, designed the figures, drafted and wrote the manuscript, T.B. and J.L. reviewed the benchmark, validated the results and reviewed the manuscript.

## Declaration of generative AI and AI-assisted technologies in the writing process

During the preparation of this work, T.B. occasionally used "DeepL" as a French-to-English translator for unique words or short expressions (up to five words). T.B. also used "Lan- guageTool" for grammar / spelling check. After using this tool, the authors reviewed and edited the content as needed and take full responsibility for the content of the published article.

## Declaration of competing interest

The authors declare no conflicts of interest regarding this manuscript.

## Data availability

The data that support the findings of this study are openly available at GitLab repository [25].

## References

[1] Pawel Gepner, Victor Gamayunov, and David L. Fraser. "Early Performance Evaluation of AVX for HPC". In: *Procedia Computer Science*. Proceedings of the International Conference on Computational Science, ICCS 2011 4 (2011), pp. 452-460. DOI: 10.1016/j.procs.2011.04.047.

[2] Cristina S. Anderson, Jingwei Zhang, and Marius Cornea. "Enhanced Vector Math Support on the Intel® AVX-512 Architecture". In: *2018 IEEE 25th Symposium on Computer Arithmetic (ARITH)*. 2018, pp. 120-124. DOI: 10.1109/ARITH.2018.8464794.

[3] Péter Mileff. "Improving Performance with SIMD Intrinsics". In: *Multidiszciplináris Tudományok* 12.1 (2022), pp. 79-89. DOI: 10.35925/j.multi.2022.1.7.
[4] Intel® AVX-512 Instructions. (accessed 2025-09-12). 2017. URL: https://www.intel.com/content/www/us/en/developer/articles/technical/intel-avx-512-instructions.html.

[5] Intel® Instruction Set Extensions Technology. (accessed 2025-09-12). 2025. URL: https://www.intel.com/content/www/us/en/support/articles/000005779.html.

[6] Hwancheol Jeong et al. Performance of SSE and AVX Instruction Sets. 2012. DOI: 10.48550/arXiv.1211.0820. arXiv: 1211.0820 [hep-lat].

[7] Xu Jinchen, Guo Shaozhong, and Wang Lei. "Optimization Technology in SIMD Mathematical Functions Based on Vector Register Reuse". In: 2012 IEEE 14th International Conference on High Performance Computing and Communication & 2012 IEEE 9th International Conference on Embedded Software and Systems. 2012, pp. 1102-1107. DOI: 10.1109/HPCC.2012.161.

[8] Somaia Awad Hassan, A. M. Hemeida, and Mountasser M. M. Mahmoud. "Performance Evaluation of Matrix-Matrix Multiplications Using Intel's Advanced Vector Extensions (AVX)". In: Microprocessors and Microsystems 47 (2016), pp. 369-374. DOI: 10.1016/j.micpro.2016.10.002.

[9] Somaia A. Hassan et al. "Effective Implementation of Matrix-Vector Multiplication on Intel's AVX Multicore Processor". In: Computer Languages, Systems & Structures 51 (2018), pp. 158-175. DOI: 10.1016/j.cl.2017.06.003.

[10] B. M. Shabanov, A. A. Rybakov, and S. S. Shumilin. "Vectorization of High-performance Scientific Calculations Using AVX-512 Instruction Set". In: Lobachevskii Journal of Mathematics 40.5 (2019), pp. 580-598. DOI: 10.1134/S1995080219050196.

[11] Juan M. Cebrian, Lasse Natvig, and Magnus Jahre. "Scalability Analysis of AVX-512 Extensions". In: The Journal of Supercomputing 76.3 (2020), pp. 2082-2097. DOI: 10.1007/s11227-019-02840-7.

[12] Pierre Fortin et al. "High-Performance SIMD Modular Arithmetic for Polynomial Evaluation". In: Currency and Computation: Practice and Experience 33.16 (2021), e6270. DOI: 10.1002/cpe.6270.

[13] Jorge Francés et al. "Performance Analysis of SSE and AVX Instructions in Multi-Core CPUs and GPU Computing on FDTD Scheme for Solid and Fluid Vibration Problems". In: The Journal of Supercomputing 70.2 (2013), pp. 514-526. DOI: 10.1007/s11227-013-1065-x.

[14] Chad Jarvis et al. "Combining Algorithmic Rethinking and AVX-512 Intrinsics for Efficient Simulation of Subcellular Calcium Signaling". In: Computational Science - ICCS 2019. Springer International Publishing, 2019, pp. 681-687. DOI: 10.1007/978-3-030-22750-0_66.

[15] Jofre Pedregosa-Gutierrez and Jim Dempsey. Direct N-Body Problem Optimisation Using the AVX-512 Instruction Set. 2021. DOI: 10.48550/arXiv.2106.11143. arXiv: 2106.11143 [physics].

[16] Sylvain Jubertie et al. "Portability across Arm NEON and SVE Vector Instruction Sets Using the NSIMD Library: A Case Study on a Seismic Spectral-Element Kernel". In: HPCS 2020. 2021. URL: https://hal.science/hal-03533584.

[17] Jakub Safarik and Vaclav Snasel. "Acceleration of Particle Swarm Optimization with AVX Instructions". In: Applied Sciences 13.2 (2023), p. 734. DOI: 10.3390/app13020734.

[18] Mathias Gottschlag, Peter Brantsch, and Frank Bellosa. "Automatic Core Specialization for AVX-512 Applications". In: Proceedings of the 13th ACM International Systems and Storage Conference. SYSTEMS '20. Association for Computing Machinery, 2020, pp. 25-35. DOI: 10.1145/3383669.3398282.

[19] Mathias Gottschlag, Tim Schmidt, and Frank Bellosa. "AVX Overhead Profiling: How Much Does Your Fast Code Slow You Down?". In: Proceedings of the 11th ACM SIGOPS Asia-Pacific Workshop on Systems. APSys '20. Association for Computing Machinery, 2020, pp. 59-66. DOI: 10.1145/3409963.3410488.
[20] GitHub - SIMD Everywhere - SIMDe. Version 0.8.2. URL: https://github.com/simd-everywhere/simde.

[21] Intel® Intrinsics Guide. (accessed 2025-09-18). 2024. URL: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html.

[22] Takashi Kusaka and Takayuki Tanaka. "Fast and Accurate Approximation Methods for Trigonometric and Arctangent Calculations for Low-Performance Computers". In: *Electronics* 11.15 (2022), p. 2285. DOI: 10.3390/electronics11152285.

[23] Przemysław Stpiczyński. "Parallel Vectorized Algorithms for Computing Trigonometric Sums Using AVX-512 Extensions". In: *Computational Science – ICCS 2024*. Ed. by Leonardo Franco et al. Springer Nature Switzerland, 2024, pp. 158–172. DOI: 10.1007/978-3-031-63778-0_12.

[24] Nikhil Dev Goyal and Parth Arora. *A Novel SIMD-Optimized Implementation for Fast and Memory-Efficient Trigonometric Computation*. 2025. DOI: 10.48550/arXiv.2502.10831. arXiv: 2502.10831 [cs].

[25] GitLab – Théo Boivin – Articles – AVX / NEON. Version 1.0.0. URL: https://gitlab.com/TheoBoivin/articles/AVX-NEON.