Why one getValues() call beats a loop of getRanges
Every call to getRange().getValue() (or getValues() on a single row) crosses the boundary between your script and the Sheets back-end. That round-trip costs roughly 50–300 ms depending on load. On a 500-row sheet, looping row-by-row turns a half-second job into a two-to-three minute crawl, and on a 5,000-row sheet you will hit the Apps Script 6-minute execution limit before you get an answer.
The fix is to pull the entire data range in one shot with sheet.getRange(2, 1, lastRow - 1, 2).getValues(). That returns a 2-D array: data[i][0] is the label column, data[i][1] is the value column. From there the accumulation is plain JavaScript — no more Sheets API calls until you want to write a result back. I keep this pattern in a utils file I paste into every automation project; it comes up constantly.
The getLastRow() call on line 7 is cheap — it reads sheet metadata, not cell data — so it does not hurt to call it once to size the range correctly. Starting at row 2 skips a header row; adjust the offset if your data starts at row 1.