VOOZH about

URL: https://dzone.com/articles/native-array-or-stdarray-that-is-the-question

โ‡ฑ Native Array or Std::Array; That Is the Question!


Related

  1. DZone
  2. Data Engineering
  3. Data
  4. Native Array or Std::Array; That Is the Question!

Native Array or Std::Array; That Is the Question!

Native array is more static, which std::array is dynamic. This article focuses on static performance specifically.

By Feb. 03, 20 ยท Tutorial
Likes
Comment
Save
22.2K Views

Join the DZone community and get the full member experience.

Join For Free

I'm a big advocate for using the C++ standard library as much as possible. Modern library implementations are fast and stable today, they are easy to use, have clear interfaces, and have unified semantics. This wasn't always the case but it has been over the past few years. here, I'm going to take a look at array-like collections in C++ โ€” specifically the std::array and std::vector types.

Both of these types integrate with various standard library algorithms in similar ways. The first is purely static; however, while the second is dynamic. Today we're going to look at static performance specifically.

You may also like: Arrays.hashCode() Vs. Objects.hash()

First, let's take a look at overall usability. Frankly, as the STL has tons of support for legacy C types, they both work pretty well:

C++




x
15


1
for(const auto& i : std_numbers)
2
{
3
    std::cout << i << std::endl;
4
}
5

 
6
for(const auto& i : a_numbers)
7
{
8
    std::cout << i << std::endl;
9
}
10

 
11
auto min = std::min_element(std_numbers.begin(), std_numbers.end());
12
std::cout << "std Min: " << *min << std::endl;
13

 
14
min = std::min_element(std::begin(a_numbers), std::end(a_numbers));
15
std::cout << "a Min: " << *min << std::endl;



There are only small differences here โ€” specifically when invoking an algorithm (i.e. std::min_element(.)). The std::array type has integrated support for iterators; we need to use adapter functions from the STL to generate an iterator from an array. This particular difference is trivial.

There's another that's not.

You can access data in an std::array using either bracket notation or via the std::array::at(.) method:

C++




xxxxxxxxxx
1
17


1
std_numbers[0] = 100;
2
for(const auto& i : std_numbers)
3
{
4
    std::cout << i << std::endl;
5
}
6

 
7
std_numbers.at(0) = 1000;
8
for(const auto& i : std_numbers)
9
{
10
    std::cout << i << std::endl;
11
}
12

 
13
a_numbers[0] = 100;
14
for(const auto& i : a_numbers)
15
{
16
    std::cout << i << std::endl;
17
}



Std::array::at(.) accesses elements in an array with bounds checking. Bracket notation does not, and neither does native arrays. This is a big deal! Out of bounds errors in arrays is a major source of significant, exploitable security vulnerabilities in software. You can avoid those issues with std::array::at(.). That by itself is reason to use std::array.

What about performance?

Let's look at a few specific operations โ€” creation, assignment, retrieval, and copying. Here's the code we'll use:

C++




xxxxxxxxxx
1
17


1
template<typename T>
2
void evaluation_engine(const T& f, const std::string& label = "Container")
3
{
4
    std::vector<double> samples;
5
    for (auto i = 0; i < sample_max; ++i)
6
   {
7
        const auto start_time = std::chrono::high_resolution_clock::now();
8
        for (auto j = 0; j < loop_max; ++j)
9
       {
10
            f();
11
       }
12
        const auto stop_time = std::chrono::high_resolution_clock::now();
13
        const auto duration = stop_time โ€” start_time;
14
        samples.push_back(duration.count());
15
   }
16
    print_statistics(samples, label);
17
}



The sample_max is 100, and the loop_max 1000. This function takes a lambda expression or function and evaluates it, timing accesses. print_statistics() will calculate a few statistics and print them (omitted).

Our creation tests are pretty straightforward:

C++




xxxxxxxxxx
1


1
evaluation_engine([]()
2
{
3
    constexpr std::array<int, 5> numbers {0, 1, 2, 3, 4};
4
}, "STL Creation");
5
evaluation_engine([]()
6
{
7
    constexpr int numbers[] {0, 1, 2, 3, 4};
8
}, "Array");



This gives us:

Plain Text




xxxxxxxxxx
1


1
Statistics for STL Creation
2
    minimum: 1787; maximum: 1859; mean: 1824.66; std: 27.2845
3

 
4
Statistics for Array
5
    minimum: 1830; maximum: 1910; mean: 1862.86; std: 27.3357



STL based creation is equivalent to native array creation. Here, it seems slightly faster, but that's likely an artifact of the state of my computer when running these tests. What about access? First, let's look at array access:

C++




xxxxxxxxxx
1


1
evaluation_engine([&a_numbers]()
2
{
3
    const auto value = a_numbers[3];
4
}, "Array Access");



Gives us these results:

Plain Text




xxxxxxxxxx
1


1
Statistics for Array Access
2
    minimum: 2147; maximum: 2241; mean: 2160.32; std: 11.3568



Now std::array:

C++




xxxxxxxxxx
1


1
evaluation_engine([&std_numbers]()
2
{
3
    const auto value = std_numbers[3];
4
}, "STL Access");


Plain Text




xxxxxxxxxx
1


1
Statistics for STL Access
2
    minimum: 3629; maximum: 3998; mean: 3760.79; std: 121.434



Using std::array::at(.)

Plain Text




xxxxxxxxxx
1


1
Statistics for STL at() Access
2
    minimum: 4320; maximum: 5094; mean: 4684.56; std: 242.694



Now looking at std::array access:

C++




xxxxxxxxxx
1


1
evaluation_engine([&std_numbers]()
2
{
3
    const auto value = std_numbers.at(3);
4
}, "STL at() Access");



Now, I'm using the high-resolution clock in these measurements, and I'm not that interested in wall clock time. I am interested in comparative performance though. Based on these measurements, we can see that native array access is a bit over 40% slower than std::array access. Std::array::at(.) access is on the order of 20% slower than that.

On my system, these measures are in nanoseconds, so this is still pretty darn fast. Nevertheless, std::array use isn't free.

What about the assignment?

Plain Text




xxxxxxxxxx
1


1
Statistics for Array assignment
2
    minimum: 2057; maximum: 2775; mean: 2205.31; std: 81.6836
3

 
4
Statistics for STL assignment
5
    minimum: 3323; maximum: 3556; mean: 3403.62; std: 28.7593
6

 
7
Statistics for STL at() Assignment
8
    minimum: 3640; maximum: 3882; mean: 3705.12; std: 42.9675



Again, a bit of a range, with std::array::at(.) being the slowest. Let's look at copying:

Plain Text




xxxxxxxxxx
1


1
Statistics for Array copy
2
    minimum: 2091; maximum: 2156; mean: 2102.45; std: 11.4301
3

 
4
Statistics for STL copy
5
    minimum: 2608; maximum: 2729; mean: 2626.83; std: 17.4178



Clearly, in all cases except initial creation, the native array is more performant than the std::array type. Keep in mind, in each of these cases, I'm performing 100,000 operations, and we're talking about a difference of a hair over two milliseconds between a native array and std::array time.

Which to use? Well, I'd suggest you use std::array in virtually all cases โ€” at least, always start with it. You may need to transition to a native array in some cases, but frankly, you can usually squeeze more performance via algorithmic improvement than changing the data structure you use.


Further Reading

Two Lines of Code and Three C++17 Features: The Overload Pattern

Hashing in Java vs. C++

How to Prevent Your Java Collections From Wasting Memory

Data structure Plain text

Opinions expressed by DZone contributors are their own.

Related

  • Reducing RAG Hallucinations With Relationship-Aware Retrieval
  • Building a Vector Index in Azure AI Search: HNSW, Profiles, and RAG Retrieval
  • Amazon OpenSearch Vector Search Explained for RAG Systems
  • Production-Grade RAG: Why Vector Search Isn't Enough (and How Hybrid Search Fills the Gaps)

Partner Resources

ร—

Comments

The likes didn't load as expected. Please refresh the page and try again.

Let's be friends: