- Dharmagya
- panchang docs
- Performance
Performance
panchang · v5.4.0 · MIT
What a call costs, how to make it cheaper with sections and computeEndTimes, and how the caches behave. Timings are from the TypeScript build; Go appears as 5.4 speedup ratios.
Measured at Pune on an Apple M3 Max laptop under Node 24, with three builds side by side: the published 4.3.1 and 5.3.0 packages from npm, and the 5.4.0 build. Each configuration ran in 11 separate processes per build, with the builds interleaved so that a scheduling stall lands on all of them, and every figure is the median. Two pure-arithmetic controls come out at 1.0× across the three builds, so the differences below are the library's, not the machine's.
Every absolute time on this page comes from the TypeScript build. Go has not been timed with this harness: its only figures here are the 5.4 speedup ratios, which come from a separate measurement. Treat the numbers as rough guidance rather than a spec, because they move with hardware, latitude and date.
Distinct days is what a calendar scan costs, where no call can reuse the last one. Same day repeated is what a UI that re-renders one date sees. The first two columns are the 5.4.0 build, and the last two the published 5.3.0 package on the same calls.
getDailyPanchang | Distinct days | Same day repeated | 5.3.0 (distinct) | 5.3.0 (same day) |
|---|---|---|---|---|
| Default (all sections + end-times) | ~0.30 ms | ~0.097 ms | ~0.41 ms | ~0.18 ms |
computeEndTimes: false | ~0.28 ms | ~0.087 ms | ~0.39 ms | ~0.17 ms |
Without 'festivals' | ~0.27 ms | not timed | ~0.41 ms | not timed |
sections: ['festivals', 'eclipse'] | ~0.30 ms | not timed | ~0.42 ms | not timed |
sections: [] | ~0.18 ms | not timed | ~0.29 ms | not timed |
sections: [] + computeEndTimes: false | ~0.16 ms | ~0.048 ms | ~0.26 ms | ~0.15 ms |
getInstantPanchang | ~0.13 ms | ~0.015 ms | ~0.22 ms | ~0.11 ms |
5.4.0 runs a default day ~1.4× faster than 5.3.0, and a repeated day ~1.9× faster. A repeated sections: [] call without end times is ~3.2× faster, and a repeated getInstantPanchang ~7×. Against 4.3.1, a default day is ~20× cheaper and a repeated day ~66×.
The two levers
Most of the cost is ephemeris evaluation, so the lever that saves real time is the one that skips ephemeris work.
| Lever | What it does | Worth |
|---|---|---|
sections | Skips the optional ephemeris-backed blocks you do not need | ~0.30 → ~0.18 ms with everything optional off, saving about 40% of a distinct day |
computeEndTimes: false | Skips the transition searches. You lose the endTime fields, and it saves arithmetic, not ephemeris work | 0 to 5% on a distinct day with every section on (inside the run-to-run noise), ~11% on top of sections: [], ~10% on a repeated day. Use it to drop fields you do not want, not to go faster |
Narrowing the work
PanchangSection lists the four optional blocks. Everything else a daily panchang returns is arithmetic over the sunrise, sunset and next-sunrise times, so it is always computed. That covers the five elements, the slot systems, muhurtas, the fixed inauspicious periods (Rahu Kalam, Gulika Kalam, Yamaganda, Dur Muhurta, Ganda Mula and Panchaka), and masa, samvat and rashi. Skipping any of it would save nothing.
| Section | Covers | Fields when omitted |
|---|---|---|
'festivals' | Festival detection, which needs the previous day, the canonical-time anchors and the next-day transit | festivals: [] — an eclipse entry is still added when 'eclipse' is on |
'eclipse' | An eclipse overlapping the Hindu day | eclipse: null |
'moonTimes' | moon.rise / moon.set | null — but moon.rise is still computed when 'festivals' is on |
'lunarWindows' | Bhadra, Varjyam and Panchaka-Rahita. Each one searches lunar longitude across the day | varjyam: [] / panchakaRahita: [] / bhadra: null — but bhadra is still computed when 'festivals' is on |
// Everything (default).
getDailyPanchang(date, loc, { timezone: 330 });
// Festivals only — no moonset, no Varjyam or Panchaka-Rahita windows.
// Moonrise and Bhadra still arrive: festival detection needs them.
getDailyPanchang(date, loc, {
timezone: 330,
sections: ['festivals', 'eclipse'],
});
// Cheapest useful call: elements, slots, muhurtas and the fixed inauspicious
// periods only. Those are arithmetic on the sunrise triplet and are always
// computed.
getDailyPanchang(date, loc, {
timezone: 330,
sections: [],
computeEndTimes: false,
});Repeated calls and the range helpers
Repeating a call for the same location and day is cheaper, and sunrise is single-valued: two calls for the same day give exactly the same time. Range helpers get the same benefit.
computeEkadashiDatesForYearreads only the tithi at sunrise: ~16 ms for a full year, against ~19 ms in 5.3.0 and ~2,410 ms in 4.3.1.computeFestivalsInRangekeeps only'festivals'and'eclipse', skips the end times, and stops each day once its festival list is complete: ~79 ms/year, against ~136 in 5.3.0 and ~2,230 in 4.3.1.computeSankrantisForYearscans one solar longitude per day and bisects the 12 transits: ~2.8 ms/year, against ~3.3 in 5.3.0 and ~160 in 4.3.1.
Where v5 is still slower
The ephemeris series are about three times more accurate against JPL DE441 than the 4.3.1 ones. Evaluating them is sine-bound, so more terms cost proportionally more time. 5.4 made each sine and cosine cheaper, which narrows the gap to 4.3.1 without closing it.
| Per call | 5.4.0 | 5.3.0 | 4.3.1 |
|---|---|---|---|
computeRashiChart / computeNavamsa | ~0.18 ms | ~0.32 ms | ~0.10 ms |
computeBhava | ~0.0064 ms | ~0.32 ms | ~0.0044 ms |
computeShadbala | ~0.20 ms | ~0.35 ms | ~0.74 ms |
computeBhavaBala | ~0.21 ms | ~0.35 ms | ~0.76 ms |
- A chart is fifteen full-accuracy planet evaluations: five planets, each at the birth instant and at two retrograde probes. That is what buys Mercury 6.50″ → 0.30″ against DE441, and why
computeRashiChartandcomputeNavamsastill cost nearly twice what they did in 4.3.1. computeBhavacomputes the lagna and the cusps and nothing else. 5.3 also computed all nine planet positions, which the cusps never read, so a house chart cost as much as a whole birth chart. It is ~50× faster than in 5.3.0, back to a few microseconds as in 4.3.1.computeShadbalaandcomputeBhavaBalaare ~3.7× and ~3.6× faster than in 4.3.1, because v5 computes the positions once and reuses them.- A raw
getSiderealSunLongitudecall on a new instant is still slower than in 4.3.1, andgetSiderealMoonLongitudeno longer is. The documented workflows go through the caches and come out faster overall. You only see this in tight loops over distinct instants, and the range APIs are the better choice there.
What 5.4 made faster
Most of the causes were repeated work. New-moon searches and planet positions were recomputed across days and charts, and are now kept in bounded caches keyed by every input, so no result depends on which calls ran before it. House cusps computed nine planet positions they never read. The converters and table builders built whole days to read four labels. Sade Sati sampled Saturn at steps where it cannot change sign, and a timezone formatter was rebuilt on every call.
One change was not about repeated work. In TypeScript, the sine and cosine behind every series evaluation passed their reduced argument through module-level variables. They now keep it in locals, which V8 can hold unboxed, and run the same operations in the same order. That change alone accounts for the rashi, navamsa, Shadbala and Bhava Bala rows above, where every call is a new birth instant and no cache is hit. It is also most of the gain in the Ekadashi and Sankranti helpers and more than half of it on a distinct default day. The caches account for nearly all of the gain on a repeated day.
None of it changed output: across the library's tests, every value and every built table is byte-identical with and without the speed work. 5.4 does move some values, as correctness fixes listed in Upgrading 5.3 → 5.4. The 5.3.0 figures on this page time the published package, so the comparison spans those fixes too.
The table below is a separate measurement on the same machine: 5.3 against 5.4 in both languages, with the two builds alternating, as medians. Each figure is a ratio, the time before divided by the time after, not an absolute time. It uses its own loops and inputs, so its ratios do not match the tables above, and for computeBhava they are far apart: 12× here against ~50× in the chart section.
| Call | TypeScript | Go |
|---|---|---|
getDailyPanchang, a year of days | 1.4× | 1.3× |
getDailyPanchang, a year in America/New_York | 1.5× | 1.4× |
getInstantPanchang, a year of instants | 1.5× | 1.4× |
computeFestivalsForYear | 1.8× | 1.5× |
buildMuhurtaTable, one year | 2.2× | 2.0× |
convertHinduToGregorian | 6.5× | 5.0× |
getUpcomingEclipses | 4.6× | 3.9× |
computeSadeSati | 18× | 16× |
computeKpCuspalSubLords / computeBhava | 12× | 38× |
| A birth-chart bundle | 2.2× | 2.5× |
React Native / Hermes
The library works with Expo and bare React Native on the Hermes engine. Pass timezone as a number. IANA strings need Intl, which older Hermes versions do not have. Render in two passes to keep the UI smooth:
import { getDailyPanchang } from 'panchang-ts';
import { InteractionManager } from 'react-native';
// Pass 1 — cheapest useful result: elements, slots, muhurtas.
const fast = getDailyPanchang(date, location, {
timezone: 330,
sections: [],
computeEndTimes: false,
});
setState(fast);
// Pass 2 — background, everything.
InteractionManager.runAfterInteractions(() => {
setState(getDailyPanchang(date, location, { timezone: 330 }));
});The TypeScript timings above are Node measurements. Hermes figures will be published once they are measured on a device. The shape of the cost carries over: ephemeris math dominates, so the sections and computeEndTimes levers save about the same proportion on any runtime. Built tables are compact on purpose, because parse time and memory are what hurt on Hermes. See the table pattern.
