From 8ac59c0d66bee11d248f5008605dc005655c1895 Mon Sep 17 00:00:00 2001 From: Yoshifumi Kawai Date: Tue, 6 Apr 2021 18:09:12 +0900 Subject: [PATCH 1/4] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 150a0c6..d9329f6 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ async void Example(int x, int y, int z) // create Utf8 StringBuilder that build Utf8 directly to avoid encoding using var sb2 = ZString.CreateUtf8StringBuilder(); - sb2.Concat("foo:", x, ", bar:", y); + sb2.AppendFormat("foo:{0} bar:{1}", x, y); // directly write to steam or dest to avoid allocation await sb2.WriteToAsync(stream); From 2aec84726d9a4edff514224557c70d9aa016118b Mon Sep 17 00:00:00 2001 From: neuecc Date: Tue, 13 Apr 2021 13:28:58 +0900 Subject: [PATCH 2/4] improve Utf16StringBuilder.Append performance --- .../Benchmarks/AppendPerformance.cs | 73 +++ .../Benchmarks/StringBuilderAppendJoin.cs | 2 + sandbox/PerfBenchmark/PerfBenchmark.csproj | 25 +- sandbox/PerfBenchmark/Program.cs | 6 +- .../Assets/Scenes/SampleScene.unity | 420 +++++++++++++++++- src/ZString.Unity/Assets/Scenes/Test.cs | 56 ++- .../ZString/Utf16ValueStringBuilder.cs | 13 +- src/ZString/Utf16ValueStringBuilder.cs | 13 +- 8 files changed, 577 insertions(+), 31 deletions(-) create mode 100644 sandbox/PerfBenchmark/Benchmarks/AppendPerformance.cs diff --git a/sandbox/PerfBenchmark/Benchmarks/AppendPerformance.cs b/sandbox/PerfBenchmark/Benchmarks/AppendPerformance.cs new file mode 100644 index 0000000..b916315 --- /dev/null +++ b/sandbox/PerfBenchmark/Benchmarks/AppendPerformance.cs @@ -0,0 +1,73 @@ +using BenchmarkDotNet.Attributes; +using Cysharp.Text; +using System; +using System.Collections.Generic; +using System.Text; + +namespace PerfBenchmark.Benchmarks +{ + [Config(typeof(BenchmarkConfig))] + public class AppendPerformance + { + List strings; + const int COUNT = 1000; + + public AppendPerformance() + { + strings = new List(); + for (int i = 0; i < 100; i++) + { + strings.Add("123456789"); + } + } + + [Benchmark] + public void ZStringUtf16() + { + for (int i = 0; i < COUNT; i++) + { + using (var sb = ZString.CreateStringBuilder()) + { + for (int j = 0; j < strings.Count; j++) + { + sb.Append(strings[j]); + } + + _ = sb.ToString(); + } + } + } + + //[Benchmark] + //public void ZStringUtf16SpanBased() + //{ + // for (int i = 0; i < COUNT; i++) + // { + // using (var sb = ZString.CreateStringBuilder()) + // { + // for (int j = 0; j < strings.Count; j++) + // { + // sb.AppendSlow(strings[j]); + // } + + // _ = sb.ToString(); + // } + // } + //} + + [Benchmark(Baseline = true)] + public void StringBuilder() + { + for (int i = 0; i < COUNT; i++) + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + for (int j = 0; j < strings.Count; j++) + { + sb.Append(strings[j]); + } + + _ = sb.ToString(); + } + } + } +} diff --git a/sandbox/PerfBenchmark/Benchmarks/StringBuilderAppendJoin.cs b/sandbox/PerfBenchmark/Benchmarks/StringBuilderAppendJoin.cs index ce701a6..f938849 100644 --- a/sandbox/PerfBenchmark/Benchmarks/StringBuilderAppendJoin.cs +++ b/sandbox/PerfBenchmark/Benchmarks/StringBuilderAppendJoin.cs @@ -17,8 +17,10 @@ public StringBuilderAppendJoin() fValues = new[] { 0f, float.MaxValue, float.MinValue }; mValues = new[] { 0m, decimal.MaxValue, decimal.MinValue }; +#if NETCOREAPP || NETSTANDARD2_1 if (StringBuilder() != ZStringBuilder()) throw new Exception(); +#endif } diff --git a/sandbox/PerfBenchmark/PerfBenchmark.csproj b/sandbox/PerfBenchmark/PerfBenchmark.csproj index 03fed0f..de19a39 100644 --- a/sandbox/PerfBenchmark/PerfBenchmark.csproj +++ b/sandbox/PerfBenchmark/PerfBenchmark.csproj @@ -1,17 +1,18 @@ - + - - Exe - netcoreapp3.1 - + + Exe + net5.0 + 8.0 + - - - - + + + + - - - + + + diff --git a/sandbox/PerfBenchmark/Program.cs b/sandbox/PerfBenchmark/Program.cs index 1563d61..690575e 100644 --- a/sandbox/PerfBenchmark/Program.cs +++ b/sandbox/PerfBenchmark/Program.cs @@ -1,6 +1,7 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Environments; using BenchmarkDotNet.Exporters; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; @@ -17,7 +18,10 @@ internal class BenchmarkConfig : ManualConfig public BenchmarkConfig() { AddDiagnoser(MemoryDiagnoser.Default); - AddJob(Job.ShortRun.WithWarmupCount(1).WithIterationCount(1)); + AddJob(Job.ShortRun.WithWarmupCount(1).WithIterationCount(1).WithRuntime(CoreRuntime.Core50)); + + // Add Targetframeworks net47 to csproj(removed for CI) + // AddJob(Job.ShortRun.WithWarmupCount(1).WithIterationCount(1).WithRuntime(ClrRuntime.Net47)); } } diff --git a/src/ZString.Unity/Assets/Scenes/SampleScene.unity b/src/ZString.Unity/Assets/Scenes/SampleScene.unity index e5ecd0a..6d49537 100644 --- a/src/ZString.Unity/Assets/Scenes/SampleScene.unity +++ b/src/ZString.Unity/Assets/Scenes/SampleScene.unity @@ -50,12 +50,11 @@ LightmapSettings: m_BounceScale: 1 m_IndirectOutputScale: 1 m_AlbedoBoost: 1 - m_TemporalCoherenceThreshold: 1 m_EnvironmentLightingMode: 0 m_EnableBakedLightmaps: 0 m_EnableRealtimeLightmaps: 0 m_LightmapEditorSettings: - serializedVersion: 10 + serializedVersion: 12 m_Resolution: 2 m_BakeResolution: 40 m_AtlasSize: 1024 @@ -63,6 +62,7 @@ LightmapSettings: m_AOMaxDistance: 1 m_CompAOExponent: 1 m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 m_Padding: 2 m_LightmapParameters: {fileID: 0} m_LightmapsBakeMode: 1 @@ -77,10 +77,16 @@ LightmapSettings: m_PVRDirectSampleCount: 32 m_PVRSampleCount: 500 m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 500 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 2 + m_PVRDenoiserTypeDirect: 0 + m_PVRDenoiserTypeIndirect: 0 + m_PVRDenoiserTypeAO: 0 m_PVRFilterTypeDirect: 0 m_PVRFilterTypeIndirect: 0 m_PVRFilterTypeAO: 0 - m_PVRFilteringMode: 1 + m_PVREnvironmentMIS: 0 m_PVRCulling: 1 m_PVRFilteringGaussRadiusDirect: 1 m_PVRFilteringGaussRadiusIndirect: 5 @@ -88,7 +94,9 @@ LightmapSettings: m_PVRFilteringAtrousPositionSigmaDirect: 0.5 m_PVRFilteringAtrousPositionSigmaIndirect: 2 m_PVRFilteringAtrousPositionSigmaAO: 1 - m_ShowResolutionOverlay: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 m_LightingDataAsset: {fileID: 0} m_UseShadowmask: 1 --- !u!196 &4 @@ -116,13 +124,15 @@ NavMeshSettings: --- !u!1 &519420028 GameObject: m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - serializedVersion: 5 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 m_Component: - component: {fileID: 519420032} - component: {fileID: 519420031} - component: {fileID: 519420029} + - component: {fileID: 519420030} m_Layer: 0 m_Name: Main Camera m_TagString: MainCamera @@ -133,20 +143,41 @@ GameObject: --- !u!81 &519420029 AudioListener: m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 519420028} m_Enabled: 1 +--- !u!114 &519420030 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 519420028} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: af7cb41eb6ddb6944ae4c758581eb5bc, type: 3} + m_Name: + m_EditorClassIdentifier: + buttonA: {fileID: 1550728148} --- !u!20 &519420031 Camera: m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 519420028} m_Enabled: 1 serializedVersion: 2 m_ClearFlags: 2 m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 @@ -176,8 +207,9 @@ Camera: --- !u!4 &519420032 Transform: m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 519420028} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: -10} @@ -186,3 +218,365 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &558967754 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 558967755} + - component: {fileID: 558967757} + - component: {fileID: 558967756} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &558967755 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 558967754} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1550728147} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &558967756 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 558967754} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Button +--- !u!222 &558967757 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 558967754} + m_CullTransparentMesh: 0 +--- !u!1 &1160431369 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1160431373} + - component: {fileID: 1160431372} + - component: {fileID: 1160431371} + - component: {fileID: 1160431370} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1160431370 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1160431369} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &1160431371 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1160431369} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 +--- !u!223 &1160431372 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1160431369} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_AdditionalShaderChannelsFlag: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &1160431373 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1160431369} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_Children: + - {fileID: 1550728147} + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &1550728146 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1550728147} + - component: {fileID: 1550728150} + - component: {fileID: 1550728149} + - component: {fileID: 1550728148} + m_Layer: 5 + m_Name: Button + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1550728147 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1550728146} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 558967755} + m_Father: {fileID: 1160431373} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -95, y: 171} + m_SizeDelta: {x: 160, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1550728148 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1550728146} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1550728149} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &1550728149 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1550728146} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &1550728150 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1550728146} + m_CullTransparentMesh: 0 +--- !u!1 &2082585984 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2082585987} + - component: {fileID: 2082585986} + - component: {fileID: 2082585985} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &2082585985 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2082585984} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &2082585986 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2082585984} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &2082585987 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2082585984} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/src/ZString.Unity/Assets/Scenes/Test.cs b/src/ZString.Unity/Assets/Scenes/Test.cs index 5f28270..240923b 100644 --- a/src/ZString.Unity/Assets/Scenes/Test.cs +++ b/src/ZString.Unity/Assets/Scenes/Test.cs @@ -1 +1,55 @@ - \ No newline at end of file + +using Cysharp.Text; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Profiling; +using UnityEngine.UI; + +public class Test : MonoBehaviour +{ + public Button buttonA; + + private void Start() + { + buttonA.onClick.AddListener(() => + { + int COUNT = 1000; + List strings = new List(); + for (int i = 0; i < 100; i++) + strings.Add("123456789"); + + + Profiler.BeginSample("Append/ZString.StringBuilder()"); + for (int i = 0; i < COUNT; i++) + { + using (var sb = ZString.CreateStringBuilder(true)) + { + for (int j = 0; j < strings.Count; j++) + { + sb.Append(strings[j]); + } + + sb.ToString(); + } + } + Profiler.EndSample(); + + + Profiler.BeginSample("Append/SharedStringBuilderScope()"); + { + for (int i = 0; i < COUNT; i++) + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + for (int j = 0; j < strings.Count; j++) + { + sb.Append(strings[j]); + } + + sb.ToString(); + // sb.Clear(); + } + } + Profiler.EndSample(); + }); + } +} \ No newline at end of file diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.cs b/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.cs index e141402..b1d4419 100644 --- a/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.cs +++ b/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.cs @@ -200,7 +200,16 @@ public void AppendLine(char value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Append(string value) { +#if UNITY_2018_3_OR_NEWER + if (buffer.Length - index < value.Length) + { + Grow(value.Length); + } + value.CopyTo(0, buffer, index, value.Length); + index += value.Length; +#else Append(value.AsSpan()); +#endif } /// Appends the string representation of a specified value followed by the default line terminator to the end of this instance. @@ -219,7 +228,7 @@ public void Append(ReadOnlySpan value) { Grow(value.Length); } - + value.CopyTo(buffer.AsSpan(index)); index += value.Length; } @@ -369,7 +378,7 @@ public void Replace(char oldChar, char newChar, int startIndex, int count) /// are removed from this builder. /// public void Replace(string oldValue, string newValue) => Replace(oldValue, newValue, 0, Length); - + public void Replace(ReadOnlySpan oldValue, ReadOnlySpan newValue) => Replace(oldValue, newValue, 0, Length); /// diff --git a/src/ZString/Utf16ValueStringBuilder.cs b/src/ZString/Utf16ValueStringBuilder.cs index e141402..b1d4419 100644 --- a/src/ZString/Utf16ValueStringBuilder.cs +++ b/src/ZString/Utf16ValueStringBuilder.cs @@ -200,7 +200,16 @@ public void AppendLine(char value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Append(string value) { +#if UNITY_2018_3_OR_NEWER + if (buffer.Length - index < value.Length) + { + Grow(value.Length); + } + value.CopyTo(0, buffer, index, value.Length); + index += value.Length; +#else Append(value.AsSpan()); +#endif } /// Appends the string representation of a specified value followed by the default line terminator to the end of this instance. @@ -219,7 +228,7 @@ public void Append(ReadOnlySpan value) { Grow(value.Length); } - + value.CopyTo(buffer.AsSpan(index)); index += value.Length; } @@ -369,7 +378,7 @@ public void Replace(char oldChar, char newChar, int startIndex, int count) /// are removed from this builder. /// public void Replace(string oldValue, string newValue) => Replace(oldValue, newValue, 0, Length); - + public void Replace(ReadOnlySpan oldValue, ReadOnlySpan newValue) => Replace(oldValue, newValue, 0, Length); /// From 2b477cb7b556e06bb30f8f375573658caf44b407 Mon Sep 17 00:00:00 2001 From: neuecc Date: Tue, 13 Apr 2021 13:59:48 +0900 Subject: [PATCH 3/4] improve Utf8ValueStringBuilder.Append performance --- .../Benchmarks/AppendPerformance.cs | 49 +++++++++++++++++++ sandbox/PerfBenchmark/Program.cs | 2 +- .../Scripts/ZString/Utf8ValueStringBuilder.cs | 13 ++++- src/ZString/Utf8ValueStringBuilder.cs | 13 ++++- 4 files changed, 72 insertions(+), 5 deletions(-) diff --git a/sandbox/PerfBenchmark/Benchmarks/AppendPerformance.cs b/sandbox/PerfBenchmark/Benchmarks/AppendPerformance.cs index b916315..5833848 100644 --- a/sandbox/PerfBenchmark/Benchmarks/AppendPerformance.cs +++ b/sandbox/PerfBenchmark/Benchmarks/AppendPerformance.cs @@ -70,4 +70,53 @@ public void StringBuilder() } } } + + + [Config(typeof(BenchmarkConfig))] + public class Utf8AppendPerformance + { + List strings; + const int COUNT = 1000; + + public Utf8AppendPerformance() + { + strings = new List(); + for (int i = 0; i < 100; i++) + { + strings.Add("123456789"); + } + } + + [Benchmark] + public void ZStringUtf8() + { + for (int i = 0; i < COUNT; i++) + { + using (var sb = ZString.CreateUtf8StringBuilder()) + { + for (int j = 0; j < strings.Count; j++) + { + sb.Append(strings[j]); + } + + _ = sb.AsSpan().ToArray(); + } + } + } + + [Benchmark(Baseline = true)] + public void StringBuilder() + { + for (int i = 0; i < COUNT; i++) + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + for (int j = 0; j < strings.Count; j++) + { + sb.Append(strings[j]); + } + + _ = sb.ToString(); + } + } + } } diff --git a/sandbox/PerfBenchmark/Program.cs b/sandbox/PerfBenchmark/Program.cs index 690575e..4d04d96 100644 --- a/sandbox/PerfBenchmark/Program.cs +++ b/sandbox/PerfBenchmark/Program.cs @@ -21,7 +21,7 @@ public BenchmarkConfig() AddJob(Job.ShortRun.WithWarmupCount(1).WithIterationCount(1).WithRuntime(CoreRuntime.Core50)); // Add Targetframeworks net47 to csproj(removed for CI) - // AddJob(Job.ShortRun.WithWarmupCount(1).WithIterationCount(1).WithRuntime(ClrRuntime.Net47)); + //AddJob(Job.ShortRun.WithWarmupCount(1).WithIterationCount(1).WithRuntime(ClrRuntime.Net47)); } } diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.cs b/src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.cs index d47df72..0c66add 100644 --- a/src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.cs +++ b/src/ZString.Unity/Assets/Scripts/ZString/Utf8ValueStringBuilder.cs @@ -41,7 +41,7 @@ static Utf8ValueStringBuilder() static byte[] scratchBuffer; [ThreadStatic] - internal static bool scratchBufferUsed; + internal static bool scratchBufferUsed; byte[] buffer; int index; @@ -199,7 +199,7 @@ public void Append(char value, int repeatCount) Advance(repeatCount); } else - { + { var maxLen = UTF8NoBom.GetMaxByteCount(1); Span utf8Bytes = stackalloc byte[maxLen]; ReadOnlySpan chars = stackalloc char[1] { value }; @@ -228,7 +228,16 @@ public void AppendLine(char value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Append(string value) { +#if UNITY_2018_3_OR_NEWER + var maxLen = UTF8NoBom.GetMaxByteCount(value.Length); + if (buffer.Length - index < maxLen) + { + Grow(maxLen); + } + index += UTF8NoBom.GetBytes(value, 0, value.Length, buffer, index); +#else Append(value.AsSpan()); +#endif } /// Appends the string representation of a specified value followed by the default line terminator to the end of this instance. diff --git a/src/ZString/Utf8ValueStringBuilder.cs b/src/ZString/Utf8ValueStringBuilder.cs index d47df72..0c66add 100644 --- a/src/ZString/Utf8ValueStringBuilder.cs +++ b/src/ZString/Utf8ValueStringBuilder.cs @@ -41,7 +41,7 @@ static Utf8ValueStringBuilder() static byte[] scratchBuffer; [ThreadStatic] - internal static bool scratchBufferUsed; + internal static bool scratchBufferUsed; byte[] buffer; int index; @@ -199,7 +199,7 @@ public void Append(char value, int repeatCount) Advance(repeatCount); } else - { + { var maxLen = UTF8NoBom.GetMaxByteCount(1); Span utf8Bytes = stackalloc byte[maxLen]; ReadOnlySpan chars = stackalloc char[1] { value }; @@ -228,7 +228,16 @@ public void AppendLine(char value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Append(string value) { +#if UNITY_2018_3_OR_NEWER + var maxLen = UTF8NoBom.GetMaxByteCount(value.Length); + if (buffer.Length - index < maxLen) + { + Grow(maxLen); + } + index += UTF8NoBom.GetBytes(value, 0, value.Length, buffer, index); +#else Append(value.AsSpan()); +#endif } /// Appends the string representation of a specified value followed by the default line terminator to the end of this instance. From 48a6d75f939c0c9d0d0c2a80aaa620b3603f57af Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 13 Apr 2021 05:00:33 +0000 Subject: [PATCH 4/4] feat: Update package.json to 2.4.1 --- src/ZString.Unity/Assets/Scripts/ZString/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZString.Unity/Assets/Scripts/ZString/package.json b/src/ZString.Unity/Assets/Scripts/ZString/package.json index ac5fa73..6f9f81e 100644 --- a/src/ZString.Unity/Assets/Scripts/ZString/package.json +++ b/src/ZString.Unity/Assets/Scripts/ZString/package.json @@ -1,7 +1,7 @@ { "name": "com.cysharp.zstring", "displayName": "ZString", - "version": "2.4.0", + "version": "2.4.1", "unity": "2018.4", "description": "Zero Allocation StringBuilder for .NET Core and Unity.", "keywords": [