Skip to content

Commit 9227568

Browse files
committed
LZ4 Encode Fast
1 parent 713f2c2 commit 9227568

2 files changed

Lines changed: 117 additions & 16 deletions

File tree

UnityAsset.NET/Compression/Compression.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ public static List<byte> CompressStream(MemoryStream uncompressedStream, string
4242
case "none":
4343
return uncompressedData.ToList();
4444
case "lz4":
45-
byte[] compressedData = new byte[LZ4Codec.MaximumOutputSize(uncompressedData.Length)];
46-
int compressedSize = LZ4Codec.Encode(uncompressedData, compressedData);
45+
byte[] compressedData = new byte[LZ4.MaximumOutputSize(uncompressedData.Length)];
46+
int compressedSize = LZ4.EncodeFast(uncompressedData, compressedData);
4747
return compressedData.Take(compressedSize).ToList();
4848
case "lz4hc":
4949
byte[] compressedDataHC = new byte[LZ4Codec.MaximumOutputSize(uncompressedData.Length)];

UnityAsset.NET/Compression/LZ4.cs

Lines changed: 115 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
namespace UnityAsset.NET;
22

3-
public static class LZ4
3+
public static unsafe class LZ4
44
{
5-
public static unsafe int Decode(ReadOnlySpan<byte> source, Span<byte> target)
5+
// Only about half the speed of K4os.Compression.LZ4
6+
7+
public static int Decode(ReadOnlySpan<byte> source, Span<byte> target)
68
{
7-
int length1 = source.Length;
8-
if (length1 <= 0)
9+
int length = source.Length;
10+
if (length < 5)
911
return 0;
1012
fixed (byte* sourcePtr = &source.GetPinnableReference())
1113
fixed (byte* targetPtr = &target.GetPinnableReference())
1214
{
1315
byte* s = sourcePtr;
1416
byte* t = targetPtr;
15-
byte* sourceEnd = sourcePtr + source.Length;
17+
byte* sourceEnd = sourcePtr + length;
1618
while (s < sourceEnd)
1719
{
1820
byte token = *s++;
@@ -27,13 +29,11 @@ public static unsafe int Decode(ReadOnlySpan<byte> source, Span<byte> target)
2729
literalLength += b;
2830
} while (b == 0xFF && s < sourceEnd);
2931
}
30-
3132
Buffer.MemoryCopy(s, t, literalLength, literalLength);
3233
s += literalLength;
3334
t += literalLength;
34-
35-
if (s >= sourceEnd) break;
36-
35+
if (s == sourceEnd && matchLength == 0) break;
36+
if (s >= sourceEnd) return -1;
3737
int offset = *s++ | (*s++ << 8);
3838
if (matchLength == 0xF)
3939
{
@@ -44,25 +44,126 @@ public static unsafe int Decode(ReadOnlySpan<byte> source, Span<byte> target)
4444
matchLength += b;
4545
} while (b == 0xFF && s < sourceEnd);
4646
}
47-
4847
matchLength += 4;
49-
5048
while (matchLength > offset)
5149
{
5250
Buffer.MemoryCopy(t - offset, t, offset, offset);
5351
t += offset;
5452
matchLength -= offset;
5553
}
56-
5754
if (matchLength > 0)
5855
{
5956
Buffer.MemoryCopy(t - offset, t, matchLength, matchLength);
6057
}
61-
6258
t += matchLength;
6359
}
64-
6560
return (int)(t - targetPtr);
6661
}
6762
}
63+
64+
public static int EncodeFast(ReadOnlySpan<byte> source, Span<byte> target)
65+
{
66+
int sourceLength = source.Length - 5;
67+
if (sourceLength < 0)
68+
{
69+
return 0;
70+
}
71+
fixed (byte* sourcePtr = &source.GetPinnableReference())
72+
fixed (byte* targetPtr = &target.GetPinnableReference())
73+
{
74+
byte* anchor = sourcePtr;
75+
IntPtr[] hashTable = new IntPtr[1 << 16];
76+
77+
byte* s = sourcePtr;
78+
byte* t = targetPtr;
79+
byte* sourceEnd = sourcePtr + sourceLength;
80+
while (s < sourceEnd - 4)
81+
{
82+
uint data = *(uint*) s;
83+
ushort hash =(ushort) ((data * 2654435761) >> 16);
84+
if (hashTable[hash] == 0)
85+
{
86+
hashTable[hash] = (IntPtr)s;
87+
s++;
88+
}
89+
else
90+
{
91+
byte* sourceOffset = (byte*)hashTable[hash];
92+
uint oldData = *(uint*) sourceOffset;
93+
if (oldData != data)
94+
{
95+
hashTable[hash] = (IntPtr)s;
96+
s++;
97+
}
98+
else
99+
{
100+
hashTable[hash] = (IntPtr)s;
101+
int matchDec = (int) (s - sourceOffset);
102+
if (matchDec > 0xFFFF)
103+
{
104+
s++;
105+
continue;
106+
}
107+
int literalLen = (int) (s - anchor);
108+
int matchLen = 4;
109+
while (s + matchLen < sourceEnd && *(s + matchLen) == *(sourceOffset + matchLen))
110+
{
111+
matchLen++;
112+
}
113+
int token = Math.Min(literalLen, 15) << 4 | Math.Min(matchLen - 4, 15);
114+
*t++ = (byte)token;
115+
if (literalLen >= 15)
116+
{
117+
int l = literalLen - 15;
118+
while (l >= 0xFF)
119+
{
120+
*t++ = 0xFF;
121+
l -= 0xFF;
122+
}
123+
*t++ = (byte)l;
124+
}
125+
Buffer.MemoryCopy(anchor, t, literalLen, literalLen);
126+
t += literalLen;
127+
byte matchDecLow = (byte)matchDec;
128+
byte matchDecHigh = (byte)(matchDec >> 8);
129+
*t++ = matchDecLow;
130+
*t++ = matchDecHigh;
131+
if (matchLen >= 19)
132+
{
133+
int l = matchLen - 19;
134+
while (l >= 0xFF)
135+
{
136+
*t++ = 0xFF;
137+
l -= 0xFF;
138+
}
139+
*t++ = (byte)l;
140+
}
141+
s += matchLen;
142+
anchor = s;
143+
}
144+
}
145+
}
146+
int literalLenFinal = (int) (sourceEnd - anchor + 5);
147+
int tokenFinal = Math.Min(literalLenFinal, 15) << 4;
148+
*t++ = (byte)tokenFinal;
149+
if (literalLenFinal >= 15)
150+
{
151+
int l = literalLenFinal - 15;
152+
while (l >= 255)
153+
{
154+
*t++ = 255;
155+
l -= 255;
156+
}
157+
*t++ = (byte)l;
158+
}
159+
Buffer.MemoryCopy(anchor, t, literalLenFinal, literalLenFinal);
160+
t += literalLenFinal;
161+
return (int)(t - targetPtr);
162+
}
163+
}
164+
165+
public static int MaximumOutputSize(int inputSize)
166+
{
167+
return inputSize + inputSize / 255 + 16;
168+
}
68169
}

0 commit comments

Comments
 (0)