Help us understand the problem. What is going on with this article?

C# の Linq が python の2倍遅い、は嘘

More than 1 year has passed since last update.

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 がそんな早くないってだけ。

おまけ

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした