Skip to content

Commit 4ca107f

Browse files
authored
Some minor performance changes for .net6+ (#149)
1. Add a new Benchmark 2. Add a useTempFile option to the constructors (breaks binary compatibility, need to add overrides). 3. Some small performance enhancements to avoid doing Marshal.Copy. 4. Add a new WriteExtraFields which takes a `params` to allow us to simplify some code .
1 parent f8cf114 commit 4ca107f

12 files changed

+199
-41
lines changed

LibZipSharp.UnitTest/ZipTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -330,22 +330,22 @@ public void SimulateXamarinAndroidUsage ([Values (true, false)] bool copyArchive
330330
}
331331

332332
[Test]
333-
public void InMemoryZipFile ()
333+
public void InMemoryZipFile ([Values (true, false)] bool useTempFile)
334334
{
335335
string filePath = Path.GetFullPath ("info.json");
336336
if (!File.Exists (filePath)) {
337337
filePath = Path.GetFullPath (Path.Combine ("LibZipSharp.UnitTest", "info.json"));
338338
}
339339
string fileRoot = Path.GetDirectoryName (filePath);
340340
using (var stream = new MemoryStream ()) {
341-
using (var zip = ZipArchive.Create (stream)) {
341+
using (var zip = ZipArchive.Create (stream, useTempFile: useTempFile)) {
342342
zip.AddFile (filePath, "info.json");
343343
zip.AddFile (Path.Combine (fileRoot, "characters_players.json"), "characters_players.json");
344344
zip.AddFile (Path.Combine (fileRoot, "object_spawn.json"), "object_spawn.json");
345345
}
346346

347347
stream.Position = 0;
348-
using (var zip = ZipArchive.Open (stream, strictConsistencyChecks: true)) {
348+
using (var zip = ZipArchive.Open (stream, strictConsistencyChecks: true, useTempFile: useTempFile)) {
349349
Assert.AreEqual (3, zip.EntryCount);
350350
foreach (var e in zip) {
351351
Console.WriteLine (e.FullName);
@@ -354,13 +354,13 @@ public void InMemoryZipFile ()
354354
}
355355

356356
stream.Position = 0;
357-
using (var zip = ZipArchive.Open (stream, strictConsistencyChecks: true)) {
357+
using (var zip = ZipArchive.Open (stream, strictConsistencyChecks: true, useTempFile: useTempFile)) {
358358
Assert.AreEqual (2, zip.EntryCount);
359359
zip.AddEntry ("info.json", File.ReadAllText (filePath), Encoding.UTF8, CompressionMethod.Deflate);
360360
}
361361

362362
stream.Position = 0;
363-
using (var zip = ZipArchive.Open (stream)) {
363+
using (var zip = ZipArchive.Open (stream, useTempFile: useTempFile)) {
364364
Assert.AreEqual (3, zip.EntryCount);
365365
Assert.IsTrue (zip.ContainsEntry ("info.json"));
366366
var entry1 = zip.ReadEntry ("info.json");

LibZipSharp/Xamarin.Tools.Zip/IPlatformServices.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public interface IPlatformServices
4848
bool SetEntryPermissions (ZipArchive archive, ulong index, EntryPermissions permissions, bool isDirectory);
4949
bool ReadAndProcessExtraFields (ZipEntry entry);
5050
bool WriteExtraFields (ZipArchive archive, ZipEntry entry, IList<ExtraField> extraFields);
51+
bool WriteExtraFields (ZipArchive archive, ZipEntry entry, params ExtraField[] extraFields);
5152
bool SetFileProperties (ZipEntry entry, string extractedFilePath, bool throwOnNativeErrors);
5253
bool ExtractSpecialFile (ZipEntry entry, string destinationDir);
5354
}

LibZipSharp/Xamarin.Tools.Zip/PlatformServices.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,15 @@ public void ReadAndProcessExtraFields (ZipEntry entry)
137137
CallServices ((IPlatformServices services) => services.ReadAndProcessExtraFields (entry));
138138
}
139139

140-
public bool WriteExtraFields (ZipArchive archive, ZipEntry entry, IList<ExtraField> extraFields = null)
140+
public bool WriteExtraFields (ZipArchive archive, ZipEntry entry, IList<ExtraField> extraFields)
141+
{
142+
if (entry == null)
143+
throw new ArgumentNullException (nameof (entry));
144+
145+
return CallServices ((IPlatformServices services) => services.WriteExtraFields (archive, entry, extraFields));
146+
}
147+
148+
public bool WriteExtraFields (ZipArchive archive, ZipEntry entry, params ExtraField[] extraFields)
141149
{
142150
if (entry == null)
143151
throw new ArgumentNullException (nameof (entry));

LibZipSharp/Xamarin.Tools.Zip/UnixPlatformServices.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,11 @@ void ForEachExtraField (IList<ExtraField> fields, bool local, Action<ExtraField>
300300
}
301301

302302
public bool WriteExtraFields (ZipArchive archive, ZipEntry entry, IList<ExtraField> extraFields)
303+
{
304+
return WriteExtraFields (archive, entry, extraFields.ToArray ());
305+
}
306+
307+
public bool WriteExtraFields (ZipArchive archive, ZipEntry entry, params ExtraField[] extraFields)
303308
{
304309
if (entry == null)
305310
return false;

LibZipSharp/Xamarin.Tools.Zip/UnixZipArchive.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ internal UnixZipArchive (string defaultExtractionDir, UnixPlatformOptions option
4444
{
4545
}
4646

47-
internal UnixZipArchive (Stream stream, UnixPlatformOptions options, OpenFlags flags) : base (stream, options, flags)
47+
internal UnixZipArchive (Stream stream, UnixPlatformOptions options, OpenFlags flags, bool useTempFile) : base (stream, options, flags, useTempFile)
4848
{
4949
}
5050

LibZipSharp/Xamarin.Tools.Zip/WindowsPlatformServices.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,11 @@ public bool StoreSpecialFile (ZipArchive archive, string sourcePath, string arch
229229
}
230230

231231
public bool WriteExtraFields (ZipArchive archive, ZipEntry entry, IList<ExtraField> extraFields)
232+
{
233+
return WriteExtraFields (archive, entry, extraFields.ToArray ());
234+
}
235+
236+
public bool WriteExtraFields (ZipArchive archive, ZipEntry entry, params ExtraField[] extraFields)
232237
{
233238
if (entry == null)
234239
return false;

LibZipSharp/Xamarin.Tools.Zip/WindowsZipArchive.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ internal WindowsZipArchive (string defaultExtractionDir, WindowsPlatformOptions
4444
{
4545
}
4646

47-
internal WindowsZipArchive (Stream stream, WindowsPlatformOptions options, OpenFlags flags) : base (stream, options, flags)
47+
internal WindowsZipArchive (Stream stream, WindowsPlatformOptions options, OpenFlags flags, bool useTempFile) : base (stream, options, flags, useTempFile)
4848
{
4949
}
5050

LibZipSharp/Xamarin.Tools.Zip/ZipArchive.Unix.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ static ZipArchive CreateArchiveInstance (string defaultExtractionDir, IPlatformO
1616
}
1717
}
1818

19-
static ZipArchive CreateInstanceFromStream (Stream stream, OpenFlags flags = OpenFlags.RDOnly, IPlatformOptions options = null)
19+
static ZipArchive CreateInstanceFromStream (Stream stream, OpenFlags flags = OpenFlags.RDOnly, IPlatformOptions options = null, bool useTempFile = true)
2020
{
2121
if (Environment.OSVersion.Platform == PlatformID.Unix) {
22-
return new UnixZipArchive (stream, EnsureOptions (options) as UnixPlatformOptions, flags);
22+
return new UnixZipArchive (stream, EnsureOptions (options) as UnixPlatformOptions, flags, useTempFile);
2323
}
2424
else {
25-
return new WindowsZipArchive (stream, EnsureOptions (options) as WindowsPlatformOptions, flags);
25+
return new WindowsZipArchive (stream, EnsureOptions (options) as WindowsPlatformOptions, flags, useTempFile);
2626
}
2727
}
2828

LibZipSharp/Xamarin.Tools.Zip/ZipArchive.cs

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
using System.Collections.Generic;
3131
using System.Diagnostics;
3232
using System.IO;
33+
using System.Net.Mime;
3334
using System.Runtime.InteropServices;
3435
using System.Text;
3536
using Xamarin.Tools.Zip.Properties;
@@ -42,9 +43,10 @@ namespace Xamarin.Tools.Zip
4243
public abstract partial class ZipArchive : IDisposable, IEnumerable <ZipEntry>
4344
{
4445
internal class CallbackContext {
45-
public Stream Source { get; set; } = null;
46-
public Stream Destination {get; set;} = null;
47-
public string DestinationFileName {get; set; } = null;
46+
public Stream Source = null;
47+
public Stream Destination = null;
48+
public string DestinationFileName = null;
49+
public bool UseTempFile = true;
4850
}
4951
public const EntryPermissions DefaultFilePermissions = EntryPermissions.OwnerRead | EntryPermissions.OwnerWrite | EntryPermissions.GroupRead | EntryPermissions.WorldRead;
5052
public const EntryPermissions DefaultDirectoryPermissions = EntryPermissions.OwnerAll | EntryPermissions.GroupRead | EntryPermissions.GroupExecute | EntryPermissions.WorldRead | EntryPermissions.WorldExecute;
@@ -106,7 +108,7 @@ internal ZipArchive (string defaultExtractionDir, IPlatformOptions options)
106108
Options = options;
107109
}
108110

109-
internal ZipArchive (Stream stream, IPlatformOptions options, OpenFlags flags = OpenFlags.RDOnly)
111+
internal ZipArchive (Stream stream, IPlatformOptions options, OpenFlags flags = OpenFlags.RDOnly, bool useTempFile = true)
110112
{
111113
if (options == null)
112114
throw new ArgumentNullException (nameof (options));
@@ -115,6 +117,7 @@ internal ZipArchive (Stream stream, IPlatformOptions options, OpenFlags flags =
115117
CallbackContext context = new CallbackContext () {
116118
Source = stream,
117119
Destination = null,
120+
UseTempFile = useTempFile,
118121
};
119122
var contextHandle = GCHandle.Alloc (context, GCHandleType.Normal);
120123
IntPtr source = Native.zip_source_function_create (callback, GCHandle.ToIntPtr (contextHandle), out errorp);
@@ -197,12 +200,13 @@ ErrorCode Open (string path, OpenFlags flags)
197200
/// <param name="stream">The stream to open</param>
198201
/// <param name="options">Platform-specific options</param>
199202
/// <param name="strictConsistencyChecks">Perform strict consistency checks.</param>
200-
public static ZipArchive Open (Stream stream, IPlatformOptions options = null, bool strictConsistencyChecks = false)
203+
/// <param name="useTempFile">Use a temporary file for the archive</param>
204+
public static ZipArchive Open (Stream stream, IPlatformOptions options = null, bool strictConsistencyChecks = false, bool useTempFile = true)
201205
{
202206
OpenFlags flags = OpenFlags.None;
203207
if (strictConsistencyChecks)
204208
flags |= OpenFlags.CheckCons;
205-
return ZipArchive.CreateInstanceFromStream (stream, flags, options);
209+
return ZipArchive.CreateInstanceFromStream (stream, flags, options, useTempFile);
206210
}
207211

208212
/// <summary>
@@ -211,12 +215,13 @@ public static ZipArchive Open (Stream stream, IPlatformOptions options = null, b
211215
/// <param name="stream">The stream to create the arhive in</param>
212216
/// <param name="options">Platform-specific options</param>
213217
/// <param name="strictConsistencyChecks">Perform strict consistency checks.</param>
214-
public static ZipArchive Create (Stream stream, IPlatformOptions options = null, bool strictConsistencyChecks = false)
218+
/// <param name="useTempFile">Use a temporary file for the archive</param>
219+
public static ZipArchive Create (Stream stream, IPlatformOptions options = null, bool strictConsistencyChecks = false, bool useTempFile = true)
215220
{
216221
OpenFlags flags = OpenFlags.Create | OpenFlags.Truncate;
217222
if (strictConsistencyChecks)
218223
flags |= OpenFlags.CheckCons;
219-
return ZipArchive.CreateInstanceFromStream (stream, flags, options);
224+
return ZipArchive.CreateInstanceFromStream (stream, flags, options, useTempFile);
220225
}
221226

222227
/// <summary>
@@ -231,8 +236,9 @@ public static ZipArchive Create (Stream stream, IPlatformOptions options = null,
231236
/// <param name="defaultExtractionDir">default target directory</param>
232237
/// <param name="strictConsistencyChecks">Perform strict consistency checks.</param>
233238
/// <param name="options">Platform-specific options, or <c>null</c> if none necessary (the default)</param>
239+
/// <param name="useTempFile">Use a temporary file for the archive</param>
234240
/// <returns>Opened ZIP archive</returns>
235-
public static ZipArchive Open (string path, FileMode mode, string defaultExtractionDir = null, bool strictConsistencyChecks = false, IPlatformOptions options = null)
241+
public static ZipArchive Open (string path, FileMode mode, string defaultExtractionDir = null, bool strictConsistencyChecks = false, IPlatformOptions options = null, bool useTempFile = true)
236242
{
237243
if (String.IsNullOrEmpty (path))
238244
throw new ArgumentException (string.Format (Resources.MustNotBeNullOrEmpty_string, nameof (path)), nameof (path));
@@ -316,7 +322,7 @@ public static ZipArchive Open (string path, FileMode mode, string defaultExtract
316322
/// Extracts all the entries from the archive and places them in the
317323
/// directory indicated by the <paramref name="destinationDirectory"/> parameter.
318324
/// If <paramref name="destinationDirectory"/> is <c>null</c> or empty, the default destination directory
319-
/// as passed to <see cref="ZipArchive.Open (string,FileMode,string,bool,IPlatformOptions)"/> is used.
325+
/// as passed to <see cref="ZipArchive.Open (string,FileMode,string,bool,IPlatformOptions, bool)"/> is used.
320326
/// </summary>
321327
/// <returns>The all.</returns>
322328
/// <param name="destinationDirectory">Destination directory.</param>
@@ -362,7 +368,7 @@ public ZipEntry AddStream (Stream stream, string archivePath, EntryPermissions p
362368
sources.Add (stream);
363369
string destPath = EnsureArchivePath (archivePath);
364370
var context = new CallbackContext () {
365-
Source = stream,
371+
Source = stream
366372
};
367373
var handle = GCHandle.Alloc (context, GCHandleType.Normal);
368374
IntPtr h = GCHandle.ToIntPtr (handle);
@@ -376,14 +382,15 @@ public ZipEntry AddStream (Stream stream, string archivePath, EntryPermissions p
376382
permissions = DefaultFilePermissions;
377383
PlatformServices.Instance.SetEntryPermissions (this, (ulong)index, permissions, false);
378384
ZipEntry entry = ReadEntry ((ulong)index);
379-
IList<ExtraField> fields = new List<ExtraField> ();
380385
ExtraField_ExtendedTimestamp timestamp = new ExtraField_ExtendedTimestamp (entry, 0, modificationTime: modificationTime ?? DateTime.UtcNow);
381-
fields.Add (timestamp);
382-
if (!PlatformServices.Instance.WriteExtraFields (this, entry, fields))
386+
timestamp.Encode ();
387+
if (!PlatformServices.Instance.WriteExtraFields (this, entry, timestamp)) {
383388
throw GetErrorException ();
389+
}
384390
return entry;
385391
}
386392

393+
387394
/// <summary>
388395
/// Adds the file to archive directory. The file is added to either the root directory of
389396
/// the ZIP archive (if <paramref name="archiveDirectory"/> is <c>null</c> or empty) or to
@@ -455,16 +462,16 @@ public ZipEntry AddFile (string sourcePath, string archivePath = null,
455462
throw GetErrorException ();
456463
PlatformServices.Instance.SetEntryPermissions (this, sourcePath, (ulong)index, permissions);
457464
ZipEntry entry = ReadEntry ((ulong)index);
458-
IList<ExtraField> fields = new List<ExtraField> ();
459465
ExtraField_ExtendedTimestamp timestamp = new ExtraField_ExtendedTimestamp (
460466
entry, 0,
461467
createTime: File.GetCreationTimeUtc (sourcePath),
462468
accessTime: File.GetLastAccessTimeUtc (sourcePath),
463469
modificationTime: File.GetLastWriteTimeUtc (sourcePath)
464470
);
465-
fields.Add (timestamp);
466-
if (!PlatformServices.Instance.WriteExtraFields (this, entry, fields))
471+
timestamp.Encode ();
472+
if (!PlatformServices.Instance.WriteExtraFields (this, entry, timestamp)) {
467473
throw GetErrorException ();
474+
}
468475
return entry;
469476
}
470477

@@ -796,7 +803,9 @@ internal ZipException GetErrorException ()
796803

797804
internal static unsafe Int64 stream_callback (IntPtr state, IntPtr data, UInt64 len, SourceCommand cmd)
798805
{
806+
#if !NET6_0_OR_GREATER
799807
byte [] buffer = null;
808+
#endif
800809
Native.zip_error_t error;
801810
int length = (int)len;
802811
var handle = GCHandle.FromIntPtr (state);
@@ -828,6 +837,14 @@ internal static unsafe Int64 stream_callback (IntPtr state, IntPtr data, UInt64
828837
return (Int64)destination.Position;
829838

830839
case SourceCommand.Write:
840+
#if NET6_0_OR_GREATER
841+
unsafe
842+
{
843+
byte* ptr = (byte*)data;
844+
destination.Write(new ReadOnlySpan<byte>(ptr, length));
845+
}
846+
return length;
847+
#else
831848
buffer = ArrayPool<byte>.Shared.Rent (length);
832849
try {
833850
Marshal.Copy (data, buffer, 0, length);
@@ -836,7 +853,7 @@ internal static unsafe Int64 stream_callback (IntPtr state, IntPtr data, UInt64
836853
} finally {
837854
ArrayPool<byte>.Shared.Return (buffer);
838855
}
839-
856+
#endif
840857
case SourceCommand.SeekWrite:
841858
Native.zip_source_args_seek_t args;
842859
if (!Native.ZipSourceGetArgs (data, len, out args)) {
@@ -895,6 +912,14 @@ internal static unsafe Int64 stream_callback (IntPtr state, IntPtr data, UInt64
895912

896913
case SourceCommand.Read:
897914
length = (int)Math.Min (stream.Length - stream.Position, length);
915+
#if NET6_0_OR_GREATER
916+
unsafe
917+
{
918+
byte* ptr = (byte*)data;
919+
int bytesRead = stream.Read(new Span<byte>(ptr, length));
920+
return bytesRead;
921+
}
922+
#else
898923
buffer = ArrayPool<byte>.Shared.Rent (length);
899924
try {
900925
int bytesRead = 0;
@@ -909,11 +934,16 @@ internal static unsafe Int64 stream_callback (IntPtr state, IntPtr data, UInt64
909934
} finally {
910935
ArrayPool<byte>.Shared.Return (buffer);
911936
}
937+
#endif
912938
case SourceCommand.BeginWrite:
913939
try {
914-
string tempFile = Path.GetTempFileName ();
915-
context.Destination = File.Open (tempFile, FileMode.OpenOrCreate, FileAccess.ReadWrite);
916-
context.DestinationFileName = tempFile;
940+
if (context.UseTempFile) {
941+
string tempFile = Path.GetTempFileName ();
942+
context.Destination = File.Open (tempFile, FileMode.OpenOrCreate, FileAccess.ReadWrite);
943+
context.DestinationFileName = tempFile;
944+
} else {
945+
context.Destination = new MemoryStream ();
946+
}
917947
} catch (IOException) {
918948
// ok use a memory stream as a backup
919949
context.Destination = new MemoryStream ();

0 commit comments

Comments
 (0)