-
-
Save dbeattie71/9ea6f6ec272c2aecc1ef609ddc49ec76 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Diagnostics; | |
using System.IO; | |
using System.IO.MemoryMappedFiles; | |
using System.Runtime.CompilerServices; | |
using System.Threading.Tasks; | |
namespace FileAnalyzer | |
{ | |
public static unsafe class NativeRecord | |
{ | |
private static readonly int[] DaysToMonth365 = | |
{ | |
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 | |
}; | |
private static readonly int[] DaysToMonth366 = | |
{ | |
0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 | |
}; | |
public static void Parse(byte* buffer, out int id, out int duration) | |
{ | |
duration = DiffTimesInSecond(buffer, buffer + 20); | |
id = ParseInt8(buffer + 40); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static int DiffTimesInSecond(byte* start, byte* end) | |
{ | |
if (*(long*)start == *(long*)end) // same year-month | |
{ | |
if (*(int*)(start + sizeof(long)) == *(int*)(end + sizeof(long))) | |
{ | |
// same day, just compare the times | |
var startTime = ParseInt2(start + 11) * 3600 + ParseInt2(start + 14) * 60 + ParseInt2(start + 17); | |
var endTime = ParseInt2(end + 11) * 3600 + ParseInt2(end + 14) * 60 + ParseInt2(end + 17); | |
return endTime - startTime; | |
} | |
else // different day in month | |
{ | |
var startTime = ParseInt2(start + 8) * 3600 * 24 + ParseInt2(start + 11) * 3600 + ParseInt2(start + 14) * 60 + ParseInt2(start + 17); | |
var endTime = ParseInt2(end + 8) * 3600 * 24 + ParseInt2(end + 11) * 3600 + ParseInt2(end + 14) * 60 + ParseInt2(end + 17); | |
return endTime - startTime; | |
} | |
} | |
return UnlikelyFullDateDiff(start, end); | |
} | |
private static int UnlikelyFullDateDiff(byte* start, byte* end) | |
{ | |
return ParseDateInSeconds(end) - ParseDateInSeconds(start); | |
} | |
private static int ParseDateInSeconds(byte* buffer) | |
{ | |
var year = ParseInt4(buffer); | |
var month = ParseInt2(buffer + 5); | |
var day = ParseInt2(buffer + 8); | |
var hour = ParseInt2(buffer + 11); | |
var min = ParseInt2(buffer + 14); | |
var sec = ParseInt2(buffer + 17); | |
var leap = (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)); | |
var days = leap ? DaysToMonth366 : DaysToMonth365; | |
var y = year - 1; | |
var n = y * 365 + y / 4 - y / 100 + y / 400 + days[month - 1] + day - 1; | |
var totalSeconds = hour * 3600 + min * 60 + sec; | |
return n * 24 * 60 * 60 + totalSeconds; | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
private static int ParseInt2(byte* buffer) | |
{ | |
return (buffer[0] - '0') * 10 + | |
(buffer[1] - '0'); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
private static int ParseInt4(byte* buffer) | |
{ | |
return (buffer[0] - '0') * 1000 | |
+ (buffer[1] - '0') * 100 | |
+ (buffer[2] - '0') * 10 | |
+ (buffer[3] - '0'); | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
public static int ParseInt8(byte* buffer) | |
{ | |
return (buffer[0] - '0') * 10000000 | |
+ (buffer[1] - '0') * 1000000 | |
+ (buffer[2] - '0') * 100000 | |
+ (buffer[3] - '0') * 10000 | |
+ (buffer[4] - '0') * 1000 | |
+ (buffer[5] - '0') * 100 | |
+ (buffer[6] - '0') * 10 | |
+ (buffer[7] - '0'); | |
} | |
} | |
internal unsafe class Program | |
{ | |
private static void Increment(ref int[] array, int id, int value) | |
{ | |
if (id < array.Length) | |
{ | |
array[id] += value; | |
return; | |
} | |
UnlikelyGrowArray(ref array, id, value); | |
} | |
private static void UnlikelyGrowArray(ref int[] array, int id, int value) | |
{ | |
var size = array.Length * 2; | |
while (id >= size) | |
size *= 2; | |
Array.Resize(ref array, size); | |
Increment(ref array, id, value); | |
} | |
private static void Main(string[] args) | |
{ | |
File.Delete("summary.txt"); | |
AppDomain.MonitoringIsEnabled = true; | |
var sp = Stopwatch.StartNew(); | |
using (var mmf = MemoryMappedFile.CreateFromFile(args[0])) | |
using (var accessor = mmf.CreateViewAccessor()) | |
{ | |
byte* buffer = null; | |
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref buffer); | |
var len = new FileInfo(args[0]).Length; | |
var entries = len / 50; | |
var tasks = new Task<int[]>[4]; | |
for (int t = 0; t < 4; t++) | |
{ | |
var start = entries / 4 * t; | |
var end = Math.Min(entries, start + entries / 4); | |
tasks[t] = Task.Run(() => | |
{ | |
var stats = new int[256]; | |
for (var i = start; i < end; i++) | |
{ | |
int id; | |
int duration; | |
NativeRecord.Parse(buffer + i * 50, out id, out duration); | |
Increment(ref stats, id, duration); | |
} | |
return stats; | |
}); | |
} | |
int[][] allStats = new int[4][]; | |
for (int i = 0; i < 4; i++) | |
{ | |
allStats[i] = tasks[i].Result; | |
} | |
WriteOutput(allStats); | |
} | |
sp.Stop(); | |
Console.WriteLine($"Took: {sp.ElapsedMilliseconds:#,#} ms and allocated " + | |
$"{AppDomain.CurrentDomain.MonitoringTotalAllocatedMemorySize / 1024:#,#} kb " + | |
$"with peak working set of {Process.GetCurrentProcess().PeakWorkingSet64 / 1024:#,#} kb"); | |
} | |
private static void WriteOutput(int[][] stats) | |
{ | |
var max = stats[0].Length; | |
for (int i = 1; i < stats.Length; i++) | |
{ | |
if (max < stats[i].Length) | |
max = stats[i].Length; | |
} | |
for (int i = 0; i < stats.Length; i++) | |
{ | |
if (max != stats[i].Length) | |
Array.Resize(ref stats[i], max); | |
} | |
using (var file = File.Create("summary.txt")) | |
{ | |
int allLines = 0; | |
file.SetLength(max * 21); | |
using (var output = MemoryMappedFile.CreateFromFile(file, "summary", max * 21, | |
MemoryMappedFileAccess.ReadWrite, null, HandleInheritability.None, leaveOpen: true)) | |
using (var accessor = output.CreateViewAccessor()) | |
{ | |
byte* buffer = null; | |
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref buffer); | |
var tasks = new Task<int>[4]; | |
for (int t = 0; t < 4; t++) | |
{ | |
var start = max / 4 * t; | |
var end = Math.Min(max, start + max / 4); | |
tasks[t] = Task.Run(() => | |
{ | |
int lines = 0; | |
var localBuffer = buffer + start*21; | |
for (var i = start; i < end; i++) | |
{ | |
var value = stats[0][i] + stats[1][i] + stats[2][i] + stats[3][i]; | |
if (value == 0) | |
continue; | |
lines++; | |
localBuffer[10] = (byte)' '; | |
localBuffer[13] = (byte)':'; | |
localBuffer[16] = (byte)':'; | |
localBuffer[19] = (byte)'\r'; | |
localBuffer[20] = (byte)'\n'; | |
WriteFormattedInt(i, localBuffer, 0, 10); | |
WriteFormattedTime(value, localBuffer, 11); | |
buffer += 21; | |
} | |
return lines; | |
}); | |
} | |
foreach (var task in tasks) | |
{ | |
allLines += task.Result; | |
} | |
accessor.SafeMemoryMappedViewHandle.ReleasePointer(); | |
} | |
file.SetLength(allLines * 21); // trim file | |
} | |
} | |
private static void WriteFormattedInt(int id, byte* temp, int pos, int numberOfDigits) | |
{ | |
var i = pos + numberOfDigits - 1; | |
do | |
{ | |
temp[i--] = (byte)(id % 10 + '0'); | |
} while ((id /= 10) > 0); | |
while (i >= pos) | |
temp[i--] = (byte)'0'; | |
} | |
private static void WriteFormattedTime(int seconds, byte* temp, int pos) | |
{ | |
var hours = seconds / 3600; | |
temp[pos] = (byte)(hours / 10 + '0'); | |
temp[pos + 1] = (byte)(hours % 10 + '0'); | |
var min = seconds / 60 - hours * 60; | |
temp[pos + 3] = (byte)(min / 10 + '0'); | |
temp[pos + 4] = (byte)(min % 10 + '0'); | |
var sec = seconds % 60; | |
temp[pos + 6] = (byte)(sec / 10 + '0'); | |
temp[pos + 7] = (byte)(sec % 10 + '0'); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment