Login Page - Create Account

Support Board


Date/Time: Tue, 03 Feb 2026 05:43:43 +0000



Post From: Suggestion for Optimizing GetMonthFromFuturesCode in Sierra Chart Headers

[2026-02-03 01:16:26]
User719512 - Posts: 411
Suggestion for Optimizing GetMonthFromFuturesCode in Sierra Chart Headers – ~12x Speedup with constexpr Lookup

Hi Sierra Chart Engineering Team,

Recently, while profiling some date/symbol parsing code in my studies, I noticed GetMonthFromFuturesCode (from the headers) uses a simple linear loop over 12 elements. It's correct and straightforward, but for hot paths like per-bar processing or GDI drawing loops (e.g., every 20ms UI refreshes), it can be optimized to a constant-time array lookup.

Here's my proposed replacement: It uses a constexpr array (compile-time initialized, zero runtime cost) indexed by uppercase char offset. This eliminates the loop entirely, dropping average call time from ~2.65 ns / ~9 cycles to ~0.21 ns / ~0.72 cycles per call in my tests (MSVC 2022 /O2 on Windows 11 x64, AMD Ryzen 9 5950X @ 3.4 GHz). For context, while this isn't a super-hot path and the gains might not be noticeable in isolation, faster is always faster, and it could add up in high-frequency scenarios, like parsing thousands of symbols or drawing custom labels.

Original Sierra Function (for reference):
enum MonthEnum { JANUARY = 1, FEBRUARY = 2, MARCH = 3, APRIL = 4, MAY = 5, JUNE = 6, JULY = 7, AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10, NOVEMBER = 11, DECEMBER = 12 };
static const char FUTURES_CODE_FOR_MONTH[13] = {'\0', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'Q', 'U', 'V', 'X', 'Z'};

inline int GetMonthFromFuturesCode(char MonthCode) {
if (MonthCode >= 'a' && MonthCode <= 'z') MonthCode += 'A' - 'a';
for (int Month = JANUARY; Month <= DECEMBER; ++Month) {
if (MonthCode == FUTURES_CODE_FOR_MONTH[Month]) return Month;
}
return 0;
}

Optimized Version:
static constexpr int MONTH_FROM_CODE[26] = {
0, 0, 0, 0, 0, // A-E (invalid)
1, 2, 3, 0, 4, // F=Jan, G=Feb, H=Mar, I=invalid, J=Apr
5, 0, 6, 7, 0, // K=May, L=invalid, M=Jun, N=Jul, O=invalid
0, 8, 0, 0, 0, // P=invalid, Q=Aug, R=invalid, S=invalid, T=invalid
9,10, 0,11, 0,12 // U=Sep, V=Oct, W=invalid, X=Nov, Y=invalid, Z=Dec
};

inline int GetMonthFromFuturesCode(char MonthCode) { // Or name it GetMonthFromFuturesCodeFast if keeping both
if (MonthCode >= 'a' && MonthCode <= 'z') MonthCode += 'A' - 'a';
if (MonthCode < 'A' || MonthCode > 'Z') return 0;
return MONTH_FROM_CODE[MonthCode - 'A'];
}

Benchmark Setup: Ran in a custom study on bar index 0 (sc.UpdateStartIndex == 0), using std::chrono::high_resolution_clock for ns timing and __rdtsc() with _mm_lfence() for cycles (via <immintrin.h>). 100,000 iterations with fixed input 'M' (June=6, mid-case) to normalize. Volatile sum to prevent optimizer elision. Sierra Chart v2874, Windows 11 x64.

Sample Results (Multiple Runs on Chart Reload):
Sierra: Avg time per call: 2.542 ns / 8.64178 cycles, Iterations: 100000, Sum: 600000
Optimized: Avg time per call: 0.213 ns / 0.7208 cycles, Iterations: 100000, Sum: 600000
Sierra: Avg time per call: 2.633 ns / 8.95016 cycles, Iterations: 100000, Sum: 600000
Optimized: Avg time per call: 0.212 ns / 0.72046 cycles, Iterations: 100000, Sum: 600000
Sierra: Avg time per call: 2.793 ns / 9.49348 cycles, Iterations: 100000, Sum: 600000
Optimized: Avg time per call: 0.212 ns / 0.7208 cycles, Iterations: 100000, Sum: 600000

Full benchmark code (two test functions for Sierra vs. Optimized):
void Test_Sierra_GetMonthFromFuturesCode(SCStudyInterfaceRef sc) {
constexpr int iterations = 100'000;
volatile int sum = 0;

auto start = std::chrono::high_resolution_clock::now();
_mm_lfence();
uint64_t start_cycles = __rdtsc();
_mm_lfence();

for (int i = 0; i < iterations; ++i) {
sum += GetMonthFromFuturesCode('M');
}

_mm_lfence();
uint64_t end_cycles = __rdtsc();
_mm_lfence();
auto end = std::chrono::high_resolution_clock::now();

auto duration_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
uint64_t total_cycles = end_cycles - start_cycles;

std::string output = std::format(
"Sierra: Avg time per call: {} ns / {} cycles, Iterations: {}, Sum (anti-optimize): {}",
static_cast<double>(duration_ns) / iterations,
static_cast<double>(total_cycles) / iterations,
iterations,
static_cast<int>(sum)
);

SCString msg(output.c_str());
sc.AddMessageToLog(msg, 1);
}

// Similar for Test_Fast_GetMonthFromFuturesCode, swapping the function call and message prefix

This is backwards-compatible (same signature/behavior), adds negligible static memory (~104 bytes), and compilers often inline it to a few instructions (original has a loop with cmp/jne; optimized is sub/mov).

For custom devs like me, we can easily override it in our studies without header changes:
#include "sierrachart.h"
#undef GetMonthFromFuturesCode // Allow redefinition

// Paste the optimized table and function here

If you don't adopt it officially, no worries, the API lets us wrapper it easily. But I figured it'd be a quick win for the core product, especially since small perf tweaks like this (loop → lookup) can compound in trading software.

Thanks for an awesome platform!