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


おまけ