-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.fs
More file actions
274 lines (225 loc) · 9.89 KB
/
Copy pathProgram.fs
File metadata and controls
274 lines (225 loc) · 9.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
open IdGen
open IronSnappy
open K4os.Compression.LZ4
open System
open System.Collections
open System.IO
open System.IO.Compression
/////////////////////////////////////////////////////////////////////////////
// Simulation tuning knobs
// Specifies the number of bits for each part of the ID structure
// (timestamp, generator ID, sequence number)
let private idStructure = new IdStructure(byte 41, byte 10, byte 12)
// Epoch of the ID format.
let private epoch = new DateTimeOffset(2010, 3, 26, 7, 3, 19, TimeSpan.Zero)
// Duration of each timestamp increment.
let private tickDuration = new TimeSpan(0, 0, 0, 0, 1, 0)
// Upper bound for simulation time increments.
let private generatedTickOffset =
int64 (TimeSpan(0, 0, 0, 30, 0, 0).TotalMilliseconds)
// The number of ID generators to use. This can be lower than what is
// allowed by the ID format.
let private idGenMax = 3
// Used to control the probability of IDs generated with the same timestamp,
// but different sequence numbers. This represents the chance, for each
// generated ID, of advancing the timestamp to a new value.
let private probabilityOfAdvancingTick = 0.7
// Controls the number of IDs that are generated for each trial.
let private numberOfIdsToGenerate = 3200
// The number of trials to run.
let private trialRounds = 5000
// The filename to which results will be output.
let private resultsFilename = "results.csv"
/////////////////////////////////////////////////////////////////////////////
type Pipeline =
{
Name: string
PipelineFunction: int64 array -> byte array
}
type Trial =
{
Pipelines: Pipeline array
IdGenerator: unit -> int64 array
}
type TrialResult =
{
Name: string
Size: int
}
type TrialResults =
{
Ids: int64 array
PipelineResults: TrialResult array
}
type MockTimeSource(maxOffset: int64, epoch: DateTimeOffset, tickDuration: TimeSpan) =
class
let mutable currentTicks = 0L
member this.NextTick() =
currentTicks <- currentTicks + Random.Shared.NextInt64(maxOffset)
interface ITimeSource with
member this.Epoch: System.DateTimeOffset = epoch
member this.GetTicks(): int64 = currentTicks
member this.TickDuration: System.TimeSpan = tickDuration
end
let timeSource = new MockTimeSource(generatedTickOffset, epoch, tickDuration)
let idGenOptions = new IdGeneratorOptions(idStructure = idStructure, timeSource = timeSource)
timeSource.NextTick()
let idGens = [|0..(idGenMax - 1)|] |> Array.map (fun n -> new IdGenerator(n, idGenOptions))
let idGenerator(count: int)() =
[|
for _ in 1..count do
let nextGen = idGens.[Random.Shared.Next(idGenMax)]
if Random.Shared.NextDouble() < probabilityOfAdvancingTick then
timeSource.NextTick()
let nextIdValue = nextGen.CreateId()
yield nextIdValue
|]
let printIdString(idValue: int64) =
printfn "%s" (Convert.ToString(idValue, 2).PadLeft(64, '0'))
let transposeIdBits(ids: int64 array) =
let workArea = new BitArray(64 * ids.Length)
let mutable nextBitIndex = 0
for j in 0..63 do
let mask = ((int64 1) <<< j)
for i in 0..(ids.Length - 1) do
let cid = ids.[i]
let nextBitValue = (cid &&& mask) <> 0
if nextBitValue then
workArea.Set(nextBitIndex, true)
nextBitIndex <- nextBitIndex + 1
let resultArray: int64 array = Array.zeroCreate ids.Length
nextBitIndex <- 0
for i in 0..(ids.Length - 1) do
let mutable nextResult: int64 = 0
for j in 0..63 do
if workArea.Get(nextBitIndex) then
nextResult <- nextResult ||| (1 <<< j)
nextBitIndex <- nextBitIndex + 1
resultArray[i] <- nextResult
resultArray
let untransposeIdBits(ids: int64 array) =
let bits = ids |> Array.map BitConverter.GetBytes |> Array.collect id
let workArea = new BitArray(bits)
let mutable nextBitIndex = 0
let resultArray: int64 array = Array.zeroCreate ids.Length
for j in 0..63 do
for i in 0..ids.Length - 1 do
if workArea.Get(nextBitIndex) then
resultArray[i] <- resultArray[i] ||| (1 <<< j)
nextBitIndex <- nextBitIndex + 1
resultArray
let compressGzip (data: int64[]) =
use memoryStream = new MemoryStream()
use gzipStream = new GZipStream(memoryStream, CompressionMode.Compress)
use writer = new BinaryWriter(gzipStream)
for value in data do
writer.Write(value)
gzipStream.Flush()
memoryStream.ToArray()
let compressBrotli (data: int64[]) =
use memoryStream = new MemoryStream()
use brotliStream = new BrotliStream(memoryStream, CompressionMode.Compress)
use writer = new BinaryWriter(brotliStream)
for value in data do
writer.Write(value)
brotliStream.Flush()
memoryStream.ToArray()
let compressZlib (data: int64[]) =
use memoryStream = new MemoryStream()
use zlibStream = new ZLibStream(memoryStream, CompressionMode.Compress)
use writer = new BinaryWriter(zlibStream)
for value in data do
writer.Write(value)
zlibStream.Flush()
memoryStream.ToArray()
let compressSnappy (data: int64[]) =
use memoryStream = new MemoryStream()
use snappyStream = Snappy.OpenWriter(memoryStream)
use writer = new BinaryWriter(snappyStream)
for value in data do
writer.Write(value)
snappyStream.Flush()
memoryStream.ToArray()
let compressLZ4 (data: int64[]) =
let bytes = data |> Array.map BitConverter.GetBytes |> Array.collect id
let output: byte array = Array.zeroCreate(LZ4Codec.MaximumOutputSize(bytes.Length))
let encodedLength = LZ4Codec.Encode(bytes, 0, bytes.Length, output, 0, output.Length, LZ4Level.L12_MAX)
let result: byte array = Array.zeroCreate encodedLength
Array.Copy(output, result, encodedLength)
result
let private groupByGeneratorId(k: int64) =
let id = idGens[0].FromId(k)
id.GeneratorId
let private groupByGeneratorIdAndSequenceNumber(k: int64) =
let id = idGens[0].FromId(k)
(id.GeneratorId, id.SequenceNumber)
let runTrial(trial: Trial): TrialResults =
let runPipeline(ids: int64 array)(pipeline: int64 array -> byte array) =
pipeline(ids)
let ids = trial.IdGenerator()
{
TrialResults.PipelineResults =
let computations =
trial.Pipelines
|> Array.map (fun pipeline -> async { return { TrialResult.Name = pipeline.Name; Size = runPipeline(ids)(pipeline.PipelineFunction) |> Array.length } })
Async.Parallel(computations)
|> Async.RunSynchronously
Ids = ids
}
let trial =
{
// The set of experiments to run in each trial. Each experiment will be run with the same set of generated IDs.
Trial.Pipelines =
[|
{ Pipeline.Name = "Original"; PipelineFunction = (fun (ids: int64 array) -> Array.zeroCreate(ids.Length * 8)) }
{ Pipeline.Name = "Gzip"; PipelineFunction = Array.sort >> compressGzip }
{ Pipeline.Name = "Gzip transposed"; PipelineFunction = Array.sort >> transposeIdBits >> compressGzip }
{ Pipeline.Name = "Brotli"; PipelineFunction = Array.sort >> compressBrotli }
{ Pipeline.Name = "Brotli transposed"; PipelineFunction = Array.sort >> transposeIdBits >> compressBrotli }
{ Pipeline.Name = "Zlib"; PipelineFunction = Array.sort >> compressZlib }
{ Pipeline.Name = "Zlib transposed"; PipelineFunction = Array.sort >> transposeIdBits >> compressZlib }
{ Pipeline.Name = "Snappy"; PipelineFunction = Array.sort >> compressSnappy }
{ Pipeline.Name = "Snappy transposed"; PipelineFunction = Array.sort >> transposeIdBits >> compressSnappy }
{ Pipeline.Name = "LZ4"; PipelineFunction = Array.sort >> compressLZ4 }
{ Pipeline.Name = "LZ4 transposed"; PipelineFunction = Array.sort >> transposeIdBits >> compressLZ4 }
{ Pipeline.Name = "LZ4 transposed & sorted by generator ID"
PipelineFunction =
Array.groupBy groupByGeneratorId
>> Array.map (fun (_, ids) -> ids |> Array.sort)
>> Array.collect id
>> transposeIdBits
>> compressLZ4 }
{ Pipeline.Name = "LZ4 transposed & sort by gen ID and seq #"
PipelineFunction =
Array.groupBy groupByGeneratorIdAndSequenceNumber
>> Array.map (fun (_, ids) -> ids |> Array.sort)
>> Array.collect id
>> transposeIdBits
>> compressLZ4 }
{ Pipeline.Name = "LZ4 transposed & descending sort by gen ID and seq #"
PipelineFunction =
Array.groupBy groupByGeneratorIdAndSequenceNumber
>> Array.map (fun (_, ids) -> ids |> Array.sortDescending)
>> Array.collect id
>> transposeIdBits
>> compressLZ4 }
{ Pipeline.Name = "Brotli transposed & sort by gen ID and seq #"
PipelineFunction =
Array.groupBy groupByGeneratorIdAndSequenceNumber
>> Array.map (fun (_, ids) -> ids |> Array.sort)
>> Array.collect id
>> transposeIdBits
>> compressBrotli }
|]
IdGenerator = idGenerator(numberOfIdsToGenerate)
}
let writer = new StreamWriter(resultsFilename)
trial.Pipelines
|> Array.iter (fun p -> writer.Write(p.Name + ","))
writer.WriteLine()
for _ in 1..trialRounds do
let results = runTrial(trial)
results.PipelineResults
|> Array.iter (fun result -> fprintf writer "%d," result.Size)
writer.WriteLine()
writer.Close()