Skip to content

Commit e2b20c8

Browse files
committed
Add thread-safe RiaStringPool utility and unit tests
Introduce RiaStringPool for efficient shared string storage and retrieval by index, with thread-safe access using shared mutex. Pre-allocate empty string at index 0. Add comprehensive unit tests covering core functionality, concurrency, edge cases, and realistic usage.
1 parent 2573c17 commit e2b20c8

File tree

5 files changed

+496
-0
lines changed

5 files changed

+496
-0
lines changed

ApplicationLibCode/Application/CMakeLists_files.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ set(SOURCE_GROUP_HEADER_FILES
2323
${CMAKE_CURRENT_LIST_DIR}/RiaEclipseFileNameTools.h
2424
${CMAKE_CURRENT_LIST_DIR}/RiaFeatureCommandContext.h
2525
${CMAKE_CURRENT_LIST_DIR}/RiaStringListSerializer.h
26+
${CMAKE_CURRENT_LIST_DIR}/RiaStringPool.h
2627
${CMAKE_CURRENT_LIST_DIR}/RiaNncDefines.h
2728
${CMAKE_CURRENT_LIST_DIR}/RiaPlotDefines.h
2829
${CMAKE_CURRENT_LIST_DIR}/RiaStimPlanModelDefines.h
@@ -67,6 +68,7 @@ set(SOURCE_GROUP_SOURCE_FILES
6768
${CMAKE_CURRENT_LIST_DIR}/RiaEclipseFileNameTools.cpp
6869
${CMAKE_CURRENT_LIST_DIR}/RiaFeatureCommandContext.cpp
6970
${CMAKE_CURRENT_LIST_DIR}/RiaStringListSerializer.cpp
71+
${CMAKE_CURRENT_LIST_DIR}/RiaStringPool.cpp
7072
${CMAKE_CURRENT_LIST_DIR}/RiaNncDefines.cpp
7173
${CMAKE_CURRENT_LIST_DIR}/RiaPlotDefines.cpp
7274
${CMAKE_CURRENT_LIST_DIR}/RiaStimPlanModelDefines.cpp
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/////////////////////////////////////////////////////////////////////////////////
2+
//
3+
// Copyright (C) 2026 Equinor ASA
4+
//
5+
// ResInsight is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
11+
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
// FITNESS FOR A PARTICULAR PURPOSE.
13+
//
14+
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
15+
// for more details.
16+
//
17+
/////////////////////////////////////////////////////////////////////////////////
18+
19+
#include "RiaStringPool.h"
20+
21+
#include <mutex>
22+
#include <stdexcept>
23+
24+
//--------------------------------------------------------------------------------------------------
25+
///
26+
//--------------------------------------------------------------------------------------------------
27+
RiaStringPool& RiaStringPool::instance()
28+
{
29+
static RiaStringPool pool;
30+
return pool;
31+
}
32+
33+
//--------------------------------------------------------------------------------------------------
34+
///
35+
//--------------------------------------------------------------------------------------------------
36+
RiaStringPool::RiaStringPool()
37+
{
38+
// Pre-allocate empty string at index 0
39+
m_strings.push_back( "" );
40+
m_stringToIndex[""] = 0;
41+
m_emptyIndex = 0;
42+
}
43+
44+
//--------------------------------------------------------------------------------------------------
45+
///
46+
//--------------------------------------------------------------------------------------------------
47+
RiaStringPool::IndexType RiaStringPool::getIndex( const std::string& str )
48+
{
49+
// First try with shared lock for reading
50+
{
51+
std::shared_lock<std::shared_mutex> lock( m_mutex );
52+
auto it = m_stringToIndex.find( str );
53+
if ( it != m_stringToIndex.end() )
54+
{
55+
return it->second;
56+
}
57+
}
58+
59+
// Need to insert, acquire exclusive lock
60+
std::unique_lock<std::shared_mutex> lock( m_mutex );
61+
62+
// Double-check after acquiring exclusive lock (another thread might have inserted it)
63+
auto it = m_stringToIndex.find( str );
64+
if ( it != m_stringToIndex.end() )
65+
{
66+
return it->second;
67+
}
68+
69+
IndexType newIndex = static_cast<IndexType>( m_strings.size() );
70+
m_strings.push_back( str );
71+
m_stringToIndex[m_strings.back()] = newIndex;
72+
return newIndex;
73+
}
74+
75+
//--------------------------------------------------------------------------------------------------
76+
///
77+
//--------------------------------------------------------------------------------------------------
78+
const std::string& RiaStringPool::getString( IndexType index ) const
79+
{
80+
std::shared_lock<std::shared_mutex> lock( m_mutex );
81+
82+
if ( index >= m_strings.size() )
83+
{
84+
throw std::out_of_range( "String pool index out of range" );
85+
}
86+
return m_strings[index];
87+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/////////////////////////////////////////////////////////////////////////////////
2+
//
3+
// Copyright (C) 2026 Equinor ASA
4+
//
5+
// ResInsight is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
11+
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
// FITNESS FOR A PARTICULAR PURPOSE.
13+
//
14+
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
15+
// for more details.
16+
//
17+
/////////////////////////////////////////////////////////////////////////////////
18+
19+
#pragma once
20+
21+
#include <cstdint>
22+
#include <shared_mutex>
23+
#include <string>
24+
#include <unordered_map>
25+
#include <vector>
26+
27+
//==================================================================================================
28+
/// String pool for memory-efficient storage of shared strings
29+
/// Stores strings once and returns indices for lookups
30+
//==================================================================================================
31+
class RiaStringPool
32+
{
33+
public:
34+
using IndexType = uint32_t;
35+
static constexpr IndexType INVALID_INDEX = static_cast<IndexType>( -1 );
36+
37+
static RiaStringPool& instance();
38+
39+
// Get or create an index for a string
40+
IndexType getIndex( const std::string& str );
41+
42+
// Get string by index
43+
const std::string& getString( IndexType index ) const;
44+
45+
// Get empty string index
46+
IndexType getEmptyIndex() const { return m_emptyIndex; }
47+
48+
private:
49+
RiaStringPool();
50+
RiaStringPool( const RiaStringPool& ) = delete;
51+
RiaStringPool& operator=( const RiaStringPool& ) = delete;
52+
53+
mutable std::shared_mutex m_mutex;
54+
std::vector<std::string> m_strings;
55+
std::unordered_map<std::string, IndexType> m_stringToIndex;
56+
IndexType m_emptyIndex;
57+
};

ApplicationLibCode/UnitTests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ set(SOURCE_UNITTEST_FILES
6969
${CMAKE_CURRENT_LIST_DIR}/RifCsvDataTableFormatter-Test.cpp
7070
${CMAKE_CURRENT_LIST_DIR}/RiaSummaryAddressAnalyzer-Test.cpp
7171
${CMAKE_CURRENT_LIST_DIR}/RiaStdStringTools-Test.cpp
72+
${CMAKE_CURRENT_LIST_DIR}/RiaStringPool-Test.cpp
7273
${CMAKE_CURRENT_LIST_DIR}/RiaInterpolationTools-Test.cpp
7374
${CMAKE_CURRENT_LIST_DIR}/RifWellMeasurementReader-Test.cpp
7475
${CMAKE_CURRENT_LIST_DIR}/RiaDateStringParser-Test.cpp

0 commit comments

Comments
 (0)