0

How can I join various strings into a string in shortest and efficient method. Currently I am doing in the following way

string str1 = string.Format(BitConverter
       .ToString(pass_packet, local_index + 511, 1) +
       BitConverter.ToString(pass_packet, local_index + 510, 1) +
       BitConverter.ToString(pass_packet, local_index + 509, 1) +
       BitConverter.ToString(pass_packet, local_index + 508, 1) + 
       ... + BitConverter.ToString(pass_packet, local_index + 400, 1));
Atish Kumar Dipongkor
  • 10,220
  • 9
  • 49
  • 77
prattom
  • 1,625
  • 11
  • 42
  • 67
  • 1
    use StringBuilder for efficiency.. – Naren Aug 21 '13 at 08:55
  • possible duplicate of [How should I concatenate strings?](http://stackoverflow.com/questions/3102806/how-should-i-concatenate-strings) – Kamil Budziewski Aug 21 '13 at 08:56
  • 1
    What is `pass_packet`, and what exactly are you trying to do? It looks like you're effectively trying to get a load of bytes *in reverse order*, represented as hex... that's a fairly unusual requirement. Is that definitely what you're trying to do? – Jon Skeet Aug 21 '13 at 08:57
  • 3
    Have you determined your approach as a performance bottleneck? – Tim Schmelter Aug 21 '13 at 08:57
  • 1
    Note that you're currently passing a single parameter to `string.Format`. The entire concatenation is carried out before the function call. If you're doing this 111 times as implied by your code, `StringBuilder` would likely both be more efficient, and would allow you to write the entire thing in a simple loop, rather than hard-coding 111 magic numbers. – David Hedlund Aug 21 '13 at 08:58

4 Answers4

5

I don't think you need to concat at all - given the test data. Take the array of bytes, reverse them and then put it through a single BitConverter.ToString call!

class Program
{
    static int ITERATIONS = 100000;
    static void Main(string[] args)
    {
        var pass_packet = Enumerable.Range(0, 1024).Select(i => (byte)i).ToArray();

        int local_index = 5;

        var sw = Stopwatch.StartNew();
        var result = StringBuilderTEST(pass_packet, local_index);

        Console.WriteLine(result + " in {0}ms", sw.ElapsedMilliseconds);

        //second option
        sw.Restart();
        var result2 = ArrayReversalTEST(pass_packet, local_index);
        Console.WriteLine(result2 + " in {0}ms", sw.ElapsedMilliseconds);

        sw.Restart();
        var result3 = ArrayReversal2TEST(pass_packet, local_index);
        Console.WriteLine(result3 + " in {0}ms", sw.ElapsedMilliseconds);

        sw.Restart();
        var result4 = StupidlyFastTEST(pass_packet, local_index);
        Console.WriteLine(result4 + " in {0}ms", sw.ElapsedMilliseconds);

        Console.WriteLine("Results are equal?  " + (result == result2 && result == result3 && result == result4));
        Console.ReadLine();
    }

    private static string StringBuilderTEST(byte[] pass_packet, int local_index)
    {
        string result = null;
        for (int b = 0; b < ITERATIONS; b++)
        {
            var sb = new StringBuilder();
            for (int i = 511; i >= 400; i--)
                sb.Append(BitConverter.ToString(pass_packet, local_index + i, 1));
            result = sb.ToString();
        }
        return result;
    }

    private static string ArrayReversalTEST(byte[] pass_packet, int local_index)
    {
        string result = null;
        for (int b = 0; b < ITERATIONS; b++)
        {
            var selectedData = pass_packet.Skip(400 + local_index).Take(112).Reverse().ToArray();
            result = BitConverter.ToString(selectedData).Replace("-", "");
        }
        return result;
    }

    private static string ArrayReversal2TEST(byte[] pass_packet, int local_index)
    {
        string result = null;
        for (int b = 0; b < ITERATIONS; b++)
        {
            var tempArray = new byte[112];
            Array.Copy(pass_packet, 400 + local_index, tempArray, 0, 112);
            Array.Reverse(tempArray);
            result = BitConverter.ToString(tempArray).Replace("-", "");
        }
        return result;
    }

    private static string StupidlyFastTEST(byte[] pass_packet, int local_index)
    {
        string result = null;
        string hex = "0123456789ABCDEF";
        for (int it = 0; it < ITERATIONS; it++)
        {
            var tempArray = new char[112 * 2];
            int tempArrayIndex = 0;
            for (int i = 511; i >= 400; i--)
            {
                var b = pass_packet[local_index + i];
                tempArray[tempArrayIndex++] = hex[b >> 4];
                tempArray[tempArrayIndex++] = hex[b & 0x0F];
            }
            result = new string(tempArray);
        }
        return result;
    }
}

Results:

Test 1 in 478ms
Test 2 in 1134ms
Test 3 in 516ms
Test 4 in 114ms
Results are equal?  True

As you can see my first two attempts at rewriting the code aren't very efficient - especially given the extra time needed to create and maintain. Some quick testing shows however that this is due to the String.Replace required to make the result identical - as the default converter places '-' between every byte pair in an array, something the original algorithm did not see due to the single byte lengths.

//Without String.Replace in tests 2 and 3
Test 1 in 475ms
Test 2 in 704ms
Test 3 in 92ms
Test 4 in 115ms
Results are equal?  False

As you can see in raw performance terms Test3 is by the fastest - though the default output contains '-' between every byte.

Test4 replaces the converter with a quick manual version - eliminating the byte separators - and is the fastest for the original result. I suspect that caching the temp array, and replacing the divisions & modulus maths* with a larger 256 element hex array would substantially speed it up, but given the point is proved will stop here.

*Edit, replaced divisions and modulus with bit operations for significant speedup.

NPSF3000
  • 2,421
  • 15
  • 20
  • Would the -1 please comment? A solution that removes the need to join strings at all will always be faster than joining them. High level optimisation. – NPSF3000 Aug 21 '13 at 09:21
  • I agree with you, but then just post the code, do not post a answer saying you will post the code.. thats a comment. – CaveCoder Aug 21 '13 at 09:23
  • @Xikinho90 It's perfectly acceptable to answer than add details later! – dav_i Aug 21 '13 at 09:32
  • @Xikinho90 the code is now available, as is results and analysis. Detailed answers can take a few seconds, but the actual answer hasn't changed from my original post (don't need to concat, look into alternatives!) – NPSF3000 Aug 21 '13 at 09:56
2

I'd do it like this:

StringBuilder sb = new StringBuilder();

for (int i = 511; i >= 400; --i)
    sb.Append(BitConverter.ToString(pass_packet, local_index + i, 1));

string str1 = sb.ToString();
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
0

The string.Concat is your friend.

string.Concat(BitConverter.ToString(pass_packet, local_index + 511, 1),
              BitConverter.ToString(pass_packet, local_index + 510, 1), 
              BitConverter.ToString(pass_packet, local_index + 509, 1),
              BitConverter.ToString(pass_packet, local_index + 508, 1),                  
              BitConverter.ToString(pass_packet, local_index + 400, 1));

There are two interesting overloads: string.Concat(params Object[] args) and string.Concat(params string[] values). Both accept a variable length of parameters (as I have done) or an array of the right type. There are even overloads that accept IEnumerable<string> or IEnumerable<T> where T is any one type.

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • 1
    I think the C# compiler already converts multiple concatenations using the "+" operator to a single call to "string.Concat()". I tried this, and inspected the result using Reflector and it calls "string.Concat()" only once after pushing all the arguments. – Matthew Watson Aug 21 '13 at 09:05
  • @MatthewWatson Yes, watching the IL code in ILSpy I can see it... It seems to call the better overload of `string.Concat` possible :-) – xanatos Aug 21 '13 at 09:10
0

It's a bit tricky, but I think this gets the work done.

string myString = new String(Enumerable.Range(400,111).SelectMany(x => BitConverter.ToString(pass_packet, x + 509, 1)).ToArray());
CaveCoder
  • 791
  • 3
  • 17