C#でLinqを使うよりPythonの方が2倍速かったのでベンチマークをしてみた

うん、まあ Linq はそこそこリッチなので、こういう使い方したらそこそこ遅くなるよね。特に GroupBy は遅い。

でもそんなことより ToList() しまくってて遅延実行の意味なし。最初の結果テーブルで C# が3倍ほど遅いのはほぼこれのせいじゃないかな。

あとシリアライザが Newtonsoft。そりゃ遅いよね、って感じ。最近のスタンダード、Utf8Json を使います。

とりあえず自分の環境で python3.6 動かした結果を貼る:

  1. 0.8537077903747559[sec]
  2. 0.8066554069519043[sec]
  3. 0.8596947193145752[sec]

0.83 秒くらいですかね?

んで C# 、愚直に書いてみる。

using System;
using System.Linq;
using System.Runtime.Serialization;
using System.IO;
using System.Diagnostics;
using Utf8Json;

namespace csharp
{
    class Program
    {
        private static readonly string _csvPath = @"C:\Users\****\Documents\work\private\real-test\test.csv";

        static void Main(string[] args)
        {
            var outPath = Path.Combine(Path.GetDirectoryName(_csvPath), "result_csharp.json");
            if (File.Exists(outPath))
            {
                File.Delete(outPath);
            }

            var stopwatch = new Stopwatch();
            stopwatch.Start();

            var lines = File.ReadLines(_csvPath)
                .Skip(1)
                .Select(line => {
                    var parts = line.Split(",");
                    float x = float.Parse(parts[2]);
                    float y = float.Parse(parts[3]);
                    int z = x > 0
                            ? (int)(x * y + 0.0000001)
                            : (int)(x * y - 0.0000001);
                    return (a: parts[0], z: z);
                })
                .GroupBy(i => i.a, i => i.z)
                .Select(g => (a: g.Key, z: g.Sum()));

            var json = JsonSerializer.SerializeUnsafe(lines);
            using (var fileStream = File.OpenWrite(outPath))
            {
                fileStream.Write(json.Array, 0, json.Count);
            }
            Console.WriteLine($"{stopwatch.Elapsed}sec");
        }
    }
}

結果こんな感じ。リリースビルドしました。

  1. 00:00:01.0881991sec
  2. 00:00:01.1056796sec
  3. 00:00:01.1177558sec

だいたい 1.10 秒くらい。0.27 秒くらい遅い。しょうがないね、GroupBy 使ってるからね。うん、C と比べて0.27 秒ほど遅いです。

これはもうちょっと早くなるよ。

using System;
using System.Linq;
using System.Runtime.Serialization;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
using Utf8Json;

namespace csharp
{
    class Program
    {
        private static readonly string _csvPath = @"C:\Users\****\Documents\work\private\real-test\test.csv";

        static void Main(string[] args)
        {
            var outPath = Path.Combine(Path.GetDirectoryName(_csvPath), "result_csharp.json");
            if (File.Exists(outPath))
            {
                File.Delete(outPath);
            }

            var stopwatch = new Stopwatch();
            stopwatch.Start();

            (string a, int z) LineSelector(string line)
            {
                    var parts = line.Split(",");
                    float x = float.Parse(parts[2]);
                    float y = float.Parse(parts[3]);
                    int z = x > 0
                            ? (int)(x * y + 0.0000001)
                            : (int)(x * y - 0.0000001);
                    return (a: parts[0], z: z);
            }

            string KeySelector((string a, int z) data) => data.a;

            int ValueSelector((string a, int z) data) => data.z;

            var lines = File.ReadLines(_csvPath)
                .Skip(1)
                .AsParallel()
                .Select(LineSelector)
                .GroupSum(KeySelector, ValueSelector);

            var json = JsonSerializer.SerializeUnsafe(lines);
            using (var fileStream = File.OpenWrite(outPath))
            {
                fileStream.Write(json.Array, 0, json.Count);
            }
            Console.WriteLine($"{stopwatch.Elapsed}sec");
        }
    }

    public static class EnumerableExtensions
    {
        public static IEnumerable<KeyValuePair<TKey, int>> GroupSum<TSource, TKey>(
            this IEnumerable<TSource> source,
            Func<TSource, TKey> keySelector,
            Func<TSource, int> valueSelector)
        {
            var dic = new Dictionary<TKey, int>();

            foreach (var item in source)
            {
                var key = keySelector(item);
                if (dic.TryGetValue(key, out var x))
                {
                    dic[key] = x + valueSelector(item);
                }
                else
                {
                    dic[key] = valueSelector(item);
                }
            }

            return dic;
        }
    }
}

修正点は以下だよ

  • GroupBy なんてリッチなもの使う必要ないから、GroupSum っていう関数を作ったよ。
  • ラムダ式よりローカル関数のほうが早いよ
  • AsParallel で並列化することで早くできるよ
  1. 00:00:00.9130819sec
  2. 00:00:00.9389940sec
  3. 00:00:00.9559440sec

0.93 秒くらい。 python より 0.10 秒遅いくらいだね。まあそんなもんです。

っていうか C で書かれたライブラリ使っていいなら C# でも C で作った DLL 呼べばいいんじゃないかな。C# のみで(もちろん Utf8Json も純 C# 製)C と競える速度なので、Linq が遅いってのは見当違いです。Linq ってか GroupBy がそんな早くないってだけ。

おまけ

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.