Skip to content

Commit

Permalink
Optimize GetExtension execution time on NET8 (#3359)
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenaw authored Jul 26, 2024
1 parent a17338c commit 8c4a5c6
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 11 deletions.
40 changes: 32 additions & 8 deletions sdk/src/Core/Amazon.Util/AWSSDKUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,31 @@ private static string DetermineValidPathCharacters()
/// <returns></returns>
public static string GetExtension(string path)
{
if (path == null)
if (path is null)
return null;

#if NET8_0_OR_GREATER
// LastIndexOf and LastIndexOfAny is vectorized on .NET8+ and is
// significantly faster for cases where 'path' does not end with a short file
// extension, such as GUIDs
ReadOnlySpan<char> pathSpan = path.AsSpan();
int extensionIndex = pathSpan.LastIndexOf('.');
if (extensionIndex == -1)
{
return string.Empty;
}

int directoryIndex = pathSpan.LastIndexOfAny('/', '\\', ':');

// extension separator is found and exists before path separator or path separator doesn't exist
// AND it's not the last one in the string
if (directoryIndex < extensionIndex && extensionIndex < pathSpan.Length - 1)
{
return pathSpan.Slice(extensionIndex).ToString();
}

return string.Empty;
#else
int length = path.Length;
int index = length;

Expand All @@ -213,15 +236,16 @@ public static string GetExtension(string path)
else if (IsPathSeparator(ch))
break;
}

return string.Empty;
}

// Checks if the character is one \ / :
private static bool IsPathSeparator(char ch)
{
return (ch == '\\' ||
ch == '/' ||
ch == ':');
bool IsPathSeparator(char ch)
{
return (ch == '\\' ||
ch == '/' ||
ch == ':');
}
#endif
}

/*
Expand Down
17 changes: 17 additions & 0 deletions sdk/test/NetStandard/UnitTests/Core/AWSSDKUtilsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,22 @@ public void UrlEncodeWithPath(string input, string expected)

Assert.Equal(expected, encoded);
}

[Theory]
[InlineData(null, null)]
[InlineData("no-delimiters-at-all", "")]
[InlineData("delimiter-end-of-string.", "")]
[InlineData("relative-path/no-file-extension", "")]
[InlineData("relative-path\\no-file-extension", "")]
[InlineData("relative-path:no-file-extension", "")]
[InlineData("simple-file.pdf", ".pdf")]
[InlineData("relative-path/with-file-extension.pdf", ".pdf")]
[InlineData("relative-path.with-dot/with-file-extension.pdf", ".pdf")]
public void GetExtension(string input, string expected)
{
var actual = AWSSDKUtils.GetExtension(input);

Assert.Equal(expected, actual);
}
}
}
23 changes: 20 additions & 3 deletions sdk/test/UnitTests/Custom/Util/AWSSDKUtilsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
using Amazon.Util;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.IO;
using Amazon.Util;
using System.Reflection;
using Moq;
using Amazon.Util.Internal;
using System.Text;

namespace AWSSDK.UnitTests
Expand Down Expand Up @@ -178,5 +176,24 @@ public void ToHex(string input, bool lowercase, string expectedResult)

Assert.AreEqual(expectedResult, hexString);
}

[TestCategory("UnitTest")]
[TestCategory("Util")]
[DataTestMethod]
[DataRow(null, null)]
[DataRow("no-delimiters-at-all", "")]
[DataRow("delimiter-end-of-string.", "")]
[DataRow("relative-path/no-file-extension", "")]
[DataRow("relative-path\\no-file-extension", "")]
[DataRow("relative-path:no-file-extension", "")]
[DataRow("simple-file.pdf", ".pdf")]
[DataRow("relative-path/with-file-extension.pdf", ".pdf")]
[DataRow("relative-path.with-dot/with-file-extension.pdf", ".pdf")]
public void GetExtension(string input, string expected)
{
var actual = AWSSDKUtils.GetExtension(input);

Assert.AreEqual(expected, actual);
}
}
}

0 comments on commit 8c4a5c6

Please sign in to comment.