DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Mastering SwiftUI Gestures: Basic to Advanced
  • RAG Done Right: When to Use SQL, Search, and Vector Retrieval and How To Combine Them
  • Why Your RAG Pipeline Will Fail Without an MCP Server
  • Designing Self-Healing AI Infrastructure: The Role of Autonomous Recovery

Trending

  • Why AI-Generated Code Breaks Your Testing Assumptions
  • Why Pass/Fail CI Pipelines Are Insufficient for Enterprise Release Decisions
  • Lambda-Driven API Design: Building Composable Node.js Endpoints With Functional Primitives
  • Key Takeaways From Integrating a RAG Application With LangSmith
  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 
Christopher Lamb user avatar
Christopher Lamb
·
Feb. 03, 20 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
22.1K 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

  • Mastering SwiftUI Gestures: Basic to Advanced
  • RAG Done Right: When to Use SQL, Search, and Vector Retrieval and How To Combine Them
  • Why Your RAG Pipeline Will Fail Without an MCP Server
  • Designing Self-Healing AI Infrastructure: The Role of Autonomous Recovery

Partner Resources

×

Comments

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

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook