Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

string.Join small optimization #8346

Closed
wants to merge 4 commits into from
Closed

string.Join small optimization #8346

wants to merge 4 commits into from

Conversation

AlexRadch
Copy link

string.Join small optimization by calling string.JoinCore

@jkotas
Copy link
Member

jkotas commented Nov 29, 2016

cc @AlexGhiondea @jamesqo

Copy link

@jamesqo jamesqo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice changes! However, you should post numbers when you're making perf optimizations, to make sure the functions you're changing are actually speeding up. I have posted instructions for doing this previously at #3163 (comment).

{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}

return Join(separator, value, 0, value.Length);
return JoinCore(&separator, 1, value, 0, value.Length);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AlexRadch I am unsure if changing the code like this will make things faster. When the code is run, the JIT compiler should inline this Join call, so that it would be effectively the same as writing JoinCore(&separator, 1, value, 0, value.Length).

Copy link
Author

@AlexRadch AlexRadch Nov 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we add some checks or something else inside Join(separator, value, 0, value.Length) later, then JIT compiler should make call not inline. So I suggests use JoinCore(&separator, 1, value, 0, value.Length) here. Also code looks more monotonous in different Join methods.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the actual performance gains from skipping this method?

{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
return Join(separator, value, 0, value.Length);
if (string.IsNullOrEmpty(separator))
return string.Concat(value);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 This should help perf, since Concat has to do less bookkeeping than Join. Please use braces here, though.

if (string.IsNullOrEmpty(separator))
{
    return string.Concat(value);
}

}

[ComVisible(false)]
public unsafe static string Join(string separator, params object[] values)
{
separator = separator ?? string.Empty;
if (string.IsNullOrEmpty(separator))
return string.Concat(values);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same nit: please use braces.


return StringBuilderCache.GetStringAndRelease(result);
// Defer argument validation to the internal function
return JoinCore(pSeparator, separator.Length, values);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now calling a generic function to do the work, which can be slower in some circumstances. Have you measured if the new implementation was faster?

Copy link
Author

@AlexRadch AlexRadch Nov 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be a little slower on calling ToString() method that does nothing for strings. So I think it does not downgrade performance but remove copy paste code from source.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AlexRadch could you provide some numbers before and after this change?

// If the separator is null, it is converted to an empty string before entering this function.
// Even for empty strings, fixed should never return null (it should return a pointer to a null char).
Contract.Assert(separator != null);
Contract.Assert(separatorLength >= 0);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be separatorLength > 0 instead? It looks like after your changes, Concat is called if the separator is null or empty.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now it true but method works correct if separatorLength equal 0, so why it should assert if separatorLength equal 0. Also third JoinCore method is called with separatorLength equal 0. So I think better to have the same asserts in all JoinCore methods.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AlexRadch If someone makes changes to this method in the future, they won't have to do any special handling of empty strings because they would know that separatorLength > 0.

// If the separator is null, it is converted to an empty string before entering this function.
// Even for empty strings, fixed should never return null (it should return a pointer to a null char).
Contract.Assert(separator != null);
Contract.Assert(separatorLength >= 0);
Copy link

@jamesqo jamesqo Nov 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here. edit: I meant in the other JoinCore method.

Copy link
Author

@AlexRadch AlexRadch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made some changes and submitted comments

@AlexGhiondea
Copy link

@AlexRadch thanks for working on this!

Could you please provide some performance measurements with and without your changes? Without that it is difficult to see the impact your change has on the performance of Join.

@AlexRadch
Copy link
Author

AlexRadch commented Nov 30, 2016

@AlexGhiondea How to create performance test for System.Private.CoreLib? Do you have any instruction or example? Also here is small optimization.

@jamesqo
Copy link

jamesqo commented Nov 30, 2016

@AlexRadch

How to create performance test for System.Private.CoreLib? Do you have any instruction or example? Also here is small optimization.

I have posted instructions previously at #3163 (comment).

@AlexRadch
Copy link
Author

Where are there string join performance tests that I can execute this old and with changed code?

@danmoseley
Copy link
Member

@AlexRadch I do not believe there are any, the approach is to make a throwaway test using the approach like @jamesqo. Please try that out and post before/after numbers for various scenarios.

@AlexRadch
Copy link
Author

@danmosemsft Write correct performance tests is hard work and it takes long time. I do not know when I can do such tests. Sorry.

@danmoseley
Copy link
Member

I do not believe it needs to be complex, eg., this one is simple: #3163 (comment)

Your call though!

@jamesqo
Copy link

jamesqo commented Dec 9, 2016

@AlexRadch I agree with @danmosemsft; it is not that hard once you get the hang of it. All you have to do is write something like this

using System.Diagnostics;

class Program
{
    // Change this depending on how long the method you are testing takes.
    const int Iterations = 10000000;

    static void Main()
    {
        for (int i = 0; i < 10; i++) // Run the benchmark 10 times
        {
            var sw = Stopwatch.StartNew(); // Start a new stopwatch while we're benchmarking

            for (int j = 0; j < Iterations; j++)
            {
                TheMethodYouAreTestingThePerformanceOf();
            }

            sw.Stop(); // Stop the stopwatch, now that we've finished benchmarking
            Console.WriteLine(sw.Elapsed); // Write how long it took to do the benchmark
        }
    }
}

And follow the instructions here for running the performance test.

@danmoseley
Copy link
Member

@AlexRadch do you expect to give this a shot? It would be interesting to verify what improvements this gives.

@danmoseley
Copy link
Member

This PR has not been updated in a month, closing. Would like to see any perf improvement for sure, feel free to reopen/create new PR if someone wants to continue here.

@danmoseley danmoseley closed this Jan 22, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
6 participants