Skip to content

Commit 744282b

Browse files
authored
Merge pull request #25 from ikelaiah/ikelaiah-rev02
Ikelaiah Revision 02
2 parents a459fb7 + 0551b7a commit 744282b

File tree

5 files changed

+131
-95
lines changed

5 files changed

+131
-95
lines changed

baseline/Lazarus/src/baseline.lpi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@
112112
<OtherUnitFiles Value="../../Common"/>
113113
<UnitOutputDirectory Value="../../../bin/lib/$(TargetCPU)-$(TargetOS)"/>
114114
</SearchPaths>
115+
<Linking>
116+
<Debugging>
117+
<DebugInfoType Value="dsDwarf2Set"/>
118+
</Debugging>
119+
</Linking>
115120
</CompilerOptions>
116121
<Debugging>
117122
<Exceptions Count="3">

entries/ikelaiah/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ The approach I implemented here is simplistic.
66

77
- Sequentially read the measurement file.
88
- Populate a `TDictionary` with station names, min, max, count and sum; without storing all the temperature measurements.
9+
- Avoided [StrUtil.SplitString](https://www.freepascal.org/docs-html/rtl/strutils/splitstring.html), used [`Pos()`](https://www.freepascal.org/docs-html/rtl/system/pos.html) and [`Copy()`](https://www.freepascal.org/docs-html/rtl/system/copy.html) instead.
910
- Use a custom comparer to sort the station and temperature statistics in a `TStringList`.
10-
- Display the sorted measurements using a simple for loop.
11+
- Use the rounding method as provided in the `baseline.lpr` (or the `README.md` from 1brc-ObjectPascal).
12+
- Display the sorted measurements using a simple `for` loop.
1113

1214
## Getting Started
1315

@@ -17,7 +19,7 @@ The approach I implemented here is simplistic.
1719

1820
### Compiling
1921

20-
* Open `OneBRC.lpi` using Lazarus.
22+
* Open `OneBRC.lpi` using Lazarus.
2123
* Hit `Ctrl + F9` to compile.
2224

2325
### Running the executable

entries/ikelaiah/src/OneBRC.lpr

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@
2222
{$mode objfpc}{$H+}{$J-}{$modeSwitch advancedRecords}
2323
{$codepage utf8}
2424

25-
2625
uses
2726
{$IFDEF UNIX}
28-
cthreads,
27+
cmem, cthreads,
2928
{$ENDIF}
3029
Classes,
3130
SysUtils,

entries/ikelaiah/src/stopwatch.pas

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,10 @@ procedure DisplayTimer;
5353
seconds := elapsedMilliseconds div 1000;
5454
milliseconds := elapsedMilliseconds mod 1000;
5555

56-
WriteLn;
5756
WriteLn('------------------------------');
5857
WriteLn('Elapsed time: ', hours, ' hours ', minutes, ' minutes ',
5958
seconds, ' seconds ', milliseconds, ' milliseconds');
60-
//WriteLn('Elapsed time: ', (endTime - startTime), ' ms');
59+
WriteLn('------------------------------');
6160
end;
6261

6362

Lines changed: 120 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,92 @@
11
unit WeatherStation;
22

33
{$mode objfpc}{$H+}{$J-}{$modeSwitch advancedRecords}
4-
54
interface
65

76
uses
87
{$IFDEF UNIX}
9-
cthreads,
8+
cmem, cthreads,
109
{$ENDIF}
1110
Classes,
1211
SysUtils,
13-
Generics.Collections
12+
Generics.Collections,
13+
Math
1414
{$IFDEF DEBUG}
1515
, Stopwatch
1616
{$ENDIF}
17-
;
17+
;
1818

1919
type
2020
// Create a record of temperature stats
2121
TStat = record
2222
var
23-
min: single;
24-
max: single;
25-
sum: single;
26-
count: word;
23+
min: int64; // Borrowed the concept from go's approach to improve performance, save floats as int64
24+
max: int64; // This saved ~2 mins processing time.
25+
sum: int64;
26+
cnt: int64;
27+
private
28+
function RoundEx(x: double): double; // Borrowed from the baseline program
29+
function PascalRound(x: double): double; // Borrowed from the baseline program
2730
public
28-
constructor Create(newMin: single;
29-
newMax: single;
30-
newSum: single;
31-
newCount: word);
31+
constructor Create(const newMin: int64; const newMax: int64;
32+
const newSum: int64; const newCount: int64);
3233
function ToString: string;
34+
3335
end;
3436

3537
type
3638
// Create a dictionary
3739
TWeatherDictionary = specialize TDictionary<string, TStat>;
3840

41+
// The main algorithm to process the temp measurements from various weather station
42+
procedure ProcessTempMeasurements(const filename: string);
3943

40-
{// A helper function to add a city temperature into the TWeatherDictionary
41-
procedure AddCityTemperature(cityName: string;
42-
newTemp: single;
43-
var weatherDictionary: TWeatherDictionary);
44-
}
44+
implementation
4545

46-
// The main algorithm to process the temp measurements from various weather station
47-
procedure ProcessTempMeasurements(filename: string);
46+
function TStat.RoundEx(x: double): double;
47+
begin
48+
Result := PascalRound(x * 10.0) / 10.0;
49+
end;
4850

51+
function TStat.PascalRound(x: double): double;
52+
var
53+
t: double;
54+
begin
55+
//round towards positive infinity
56+
t := Trunc(x);
57+
if (x < 0.0) and (t - x = 0.5) then
58+
begin
59+
// Do nothing
60+
end
61+
else if Abs(x - t) >= 0.5 then
62+
begin
63+
t := t + Math.Sign(x);
64+
end;
4965

50-
implementation
66+
if t = 0.0 then
67+
Result := 0.0
68+
else
69+
Result := t;
70+
end;
5171

52-
constructor TStat.Create(newMin: single;
53-
newMax: single;
54-
newSum: single;
55-
newCount: word);
72+
constructor TStat.Create(const newMin: int64; const newMax: int64;
73+
const newSum: int64; const newCount: int64);
5674
begin
5775
self.min := newMin;
5876
self.max := newMax;
5977
self.sum := newSum;
60-
self.count := newCount;
78+
self.cnt := newCount;
6179
end;
6280

6381
function TStat.ToString: string;
82+
var
83+
minR, meanR, maxR: double; // Store the rounded values prior saving to TStringList.
6484
begin
65-
{$IFDEF DEBUG}
66-
Result := Format('Min: %.1f; Mean: %.1f; Maxp: %.1f; Sum: %.1f; Count %d',
67-
[self.min, (self.sum / self.Count), self.max,
68-
self.sum, self.Count]);
69-
{$ENDIF DEBUG}
70-
// Result := Format('%.1f/%.1f/%.1f', [self.min, (self.sum / self.count), self.max]);
71-
Result := FormatFloat('0.0', self.min) + '/' + FormatFloat('0.0', (self.sum/self.count)) + '/' + FormatFloat('0.0', self.max)
72-
85+
minR := RoundEx(self.min / 10);
86+
maxR := RoundEx(self.max / 10);
87+
meanR := RoundEx(self.sum / self.cnt / 10);
88+
Result := FormatFloat('0.0', minR) + '/' + FormatFloat('0.0', meanR) +
89+
'/' + FormatFloat('0.0', maxR);
7390
end;
7491

7592
{
@@ -78,10 +95,11 @@ function TStat.ToString: string;
7895
The following procedure Written by Székely Balázs for the 1BRC for Object Pascal.
7996
URL: https://github.com/gcarreno/1brc-ObjectPascal/tree/main
8097
}
81-
function CustomTStringListComparer(AList: TStringList; AIndex1, AIndex2: Integer): Integer;
98+
function CustomTStringListComparer(AList: TStringList;
99+
AIndex1, AIndex2: integer): integer;
82100
var
83-
Pos1, Pos2: Integer;
84-
Str1, Str2: String;
101+
Pos1, Pos2: integer;
102+
Str1, Str2: string;
85103
begin
86104
Result := 0;
87105
Str1 := AList.Strings[AIndex1];
@@ -92,24 +110,19 @@ function CustomTStringListComparer(AList: TStringList; AIndex1, AIndex2: Integer
92110
begin
93111
Str1 := Copy(Str1, 1, Pos1 - 1);
94112
Str2 := Copy(Str2, 1, Pos2 - 1);
95-
Result := CompareStr(Str1, Str2);
113+
Result := CompareStr(Str1, Str2);
96114
end;
97115
end;
98116

99-
procedure AddCityTemperature(cityName: string;
100-
newTemp: single;
101-
var weatherDictionary: TWeatherDictionary);
117+
118+
procedure AddCityTemperature(const cityName: string; const newTemp: int64;
119+
var weatherDictionary: TWeatherDictionary);
102120
var
103121
stat: TStat;
104122
begin
105-
// If city name exists, modify temp as needed
123+
// If city name esxists, modify temp as needed
106124
if weatherDictionary.ContainsKey(cityName) then
107125
begin
108-
109-
{$IFDEF DEBUG}
110-
WriteLn('City found: ', cityName);
111-
{$ENDIF DEBUG}
112-
113126
// Get the temp record
114127
stat := weatherDictionary[cityName];
115128

@@ -123,7 +136,7 @@ procedure AddCityTemperature(cityName: string;
123136
stat.sum := stat.sum + newTemp;
124137

125138
// Increase the counter
126-
stat.Count := stat.Count + 1;
139+
stat.cnt := stat.cnt + 1;
127140

128141
// Update the stat of this city
129142
weatherDictionary.AddOrSetValue(cityName, stat);
@@ -133,27 +146,20 @@ procedure AddCityTemperature(cityName: string;
133146
if not weatherDictionary.ContainsKey(cityName) then
134147
begin
135148
weatherDictionary.Add(cityName, TStat.Create(newTemp, newTemp, newTemp, 1));
136-
{$IFDEF DEBUG}
137-
WriteLn('Added: ', cityName);
138-
{$ENDIF DEBUG}
139149
end;
140150
end;
141151

142-
procedure ProcessTempMeasurements(filename: string);
152+
procedure ProcessTempMeasurements(const filename: string);
143153
var
144154
wd: TWeatherDictionary;
145-
line, ws: string;
146-
lineSeparated: array of string;
155+
line, ws, strTemp: string;
147156
weatherStationList: TStringList;
148157
textFile: System.TextFile;
149-
isFirstKey: boolean = True;
158+
delimiterPos, valCode: integer;
159+
intTemp: int64;
160+
index: integer;
150161
begin
151162

152-
// Start a timer
153-
{$IFDEF DEBUG}
154-
Stopwatch.StartTimer;
155-
{$ENDIF}
156-
157163
// Create a city - weather dictionary
158164
wd := TWeatherDictionary.Create;
159165
weatherStationList := TStringList.Create;
@@ -167,18 +173,36 @@ procedure ProcessTempMeasurements(filename: string);
167173
// Open the file for reading
168174
Reset(textFile);
169175

176+
{$IFDEF DEBUG}
177+
// Start a timer
178+
Stopwatch.StartTimer;
179+
{$ENDIF}
180+
170181
// Keep reading lines until the end of the file is reached
171182
while not EOF(textFile) do
172183
begin
173184
// Read a line
174185
ReadLn(textFile, line);
175-
// If the line start with #, then continue/skip.
176-
if (line[1] = '#') then continue;
177-
178-
// Else, add an entry into the dictionary.
179-
lineSeparated := line.Split([';']);
180-
AddCityTemperature(lineSeparated[0], StrToFloat(lineSeparated[1]), wd);
181186

187+
// Get position of the delimiter
188+
delimiterPos := Pos(';', line);
189+
if delimiterPos > 0 then
190+
begin
191+
// Get the weather station name
192+
// Using Copy and POS - as suggested by Gemini AI.
193+
// This part saves 3 mins faster when processing 1 billion rows.
194+
ws := Copy(line, 1, delimiterPos - 1);
195+
196+
// Get the temperature recorded, as string, remove '.' from string float
197+
// because we want to save it as int64.
198+
strTemp := Copy(line, delimiterPos + 1, Length(line));
199+
strTemp := StringReplace(strTemp, '.', '', [rfReplaceAll]);
200+
201+
// Add the weather station and the recorded temp (as int64) in the TDictionary
202+
Val(strTemp, intTemp, valCode);
203+
if valCode <> 0 then Continue;
204+
AddCityTemperature(ws, intTemp, wd);
205+
end;
182206
end; // end while loop reading line at a time
183207

184208
// Close the file
@@ -189,45 +213,52 @@ procedure ProcessTempMeasurements(filename: string);
189213
WriteLn('File handling error occurred. Details: ', E.Message);
190214
end; // End of file reading ////////////////////////////////////////////////
191215

216+
{$IFDEF DEBUG}
217+
Stopwatch.StopTimer;
218+
WriteLn('Finished reading and updating dictionary');
219+
Stopwatch.DisplayTimer;
220+
{$ENDIF}
221+
192222
// Format and sort weather station by name and temp stat ///////////////////
223+
{$IFDEF DEBUG}
224+
Stopwatch.StartTimer;
225+
{$ENDIF}
226+
ws := '';
193227
for ws in wd.Keys do
194228
begin
195-
weatherStationList.Add(ws + '=' + wd[ws].ToString);
229+
weatherStationList.Add(ws + '=' + wd[ws].ToString + ', ');
196230
end;
197231
weatherStationList.CustomSort(@CustomTStringListComparer);
198232

199-
// Print TStringList - sorted by weather station and temp stat /////////////
200-
Write('{');
201-
for ws in weatherStationList do
202-
begin
203-
// If it's not the first key, print a comma
204-
if not isFirstKey then
205-
Write(', ');
206-
207-
// Print the weather station and the temp stat
208-
Write(ws);
209-
210-
// Set isFirstKey to False after printing the first key
211-
isFirstKey := False;
212-
end;
233+
{$IFDEF DEBUG}
234+
Stopwatch.StopTimer;
235+
WriteLn('Finished creating TStringList and sorted it');
236+
Stopwatch.DisplayTimer;
237+
{$ENDIF}
213238

214-
WriteLn('}');
239+
// Print TStringList - sorted by weather station and temp stat /////////////
240+
{$IFDEF DEBUG}
241+
Stopwatch.StartTimer;
242+
{$ENDIF}
243+
strTemp := '';
244+
// Print the weather station and the temp stat
245+
for index := 0 to weatherStationList.Count - 1 do
246+
strTemp := strTemp + weatherStationList[index];
247+
// Remove last comma and space; ', ', a neat trick from Gus.
248+
SetLength(strTemp, Length(strTemp) - 2);
249+
WriteLn('{', strTemp, '}');
215250

216251
{$IFDEF DEBUG}
217-
WriteLn('DEBUG mode on');
218-
{$ENDIF DEBUG}
252+
Stopwatch.StopTimer;
253+
WriteLn('Finished printing the sorted weather station and temperatures');
254+
Stopwatch.DisplayTimer;
255+
{$ENDIF}
219256

220257
finally
221258
weatherStationList.Free;
222259
wd.Free;
223260
end; // End of processing TDictionary and TStringList
224261

225-
// Stop a timer
226-
{$IFDEF DEBUG}
227-
Stopwatch.StopTimer;
228-
Stopwatch.DisplayTimer;
229-
{$ENDIF}
230-
231262
end;
232263

233264
end.

0 commit comments

Comments
 (0)