Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
60
Help us understand the problem. What is going on with this article?

More than 3 years have passed since last update.

@Akira_Kido_N

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

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

おまけ

60
Help us understand the problem. What is going on with this article?
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
60
Help us understand the problem. What is going on with this article?