Skip to content

Instantly share code, notes, and snippets.

@ayende
Last active June 13, 2017 23:04
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ayende/6929f3d4e965924ceb76ea5cffbca9ea to your computer and use it in GitHub Desktop.
Save ayende/6929f3d4e965924ceb76ea5cffbca9ea to your computer and use it in GitHub Desktop.
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