rufio 0.32.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +65 -0
- data/README.md +63 -7
- data/README_EN.md +25 -0
- data/docs/CHANGELOG_v0.34.0.md +444 -0
- data/docs/file-preview-optimization-analysis.md +759 -0
- data/docs/file-preview-performance-issue-FIXED.md +547 -0
- data/lib/rufio/application.rb +9 -1
- data/lib/rufio/async_scanner_fiber.rb +154 -0
- data/lib/rufio/async_scanner_promise.rb +66 -0
- data/lib/rufio/background_command_executor.rb +98 -0
- data/lib/rufio/command_logger.rb +121 -0
- data/lib/rufio/command_mode.rb +17 -2
- data/lib/rufio/directory_listing.rb +60 -12
- data/lib/rufio/keybind_handler.rb +73 -2
- data/lib/rufio/native/rufio_zig.bundle +0 -0
- data/lib/rufio/native_scanner.rb +325 -0
- data/lib/rufio/native_scanner_zig.rb +354 -0
- data/lib/rufio/parallel_scanner.rb +173 -0
- data/lib/rufio/terminal_ui.rb +66 -16
- data/lib/rufio/version.rb +1 -1
- data/lib/rufio.rb +7 -0
- data/lib_zig/rufio_native/Makefile +34 -0
- data/lib_zig/rufio_native/build.zig +45 -0
- data/lib_zig/rufio_native/src/main.zig +378 -0
- data/lib_zig/rufio_native/src/main.zig.sync +205 -0
- metadata +17 -2
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
# FilePreview パフォーマンス最適化分析レポート
|
|
2
|
+
|
|
3
|
+
## エグゼクティブサマリー
|
|
4
|
+
|
|
5
|
+
Rufio ファイルマネージャーのテキストビューワ(FilePreviewクラス)のパフォーマンス分析を実施し、複数の改善方式を検討しました。
|
|
6
|
+
|
|
7
|
+
**主要な発見:**
|
|
8
|
+
- 現在の実装は既に高速(50行: 0.056ms、1000行: 0.193ms)
|
|
9
|
+
- ボトルネックはテキストファイル読み込み(全体の79.3%)
|
|
10
|
+
- 最も効果的な改善方式: **Zig ネイティブ実装**(2-3倍高速化の見込み)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 目次
|
|
15
|
+
|
|
16
|
+
1. [現在の実装分析](#現在の実装分析)
|
|
17
|
+
2. [パフォーマンスベンチマーク結果](#パフォーマンスベンチマーク結果)
|
|
18
|
+
3. [ボトルネック特定](#ボトルネック特定)
|
|
19
|
+
4. [改善方式の比較](#改善方式の比較)
|
|
20
|
+
5. [推奨事項](#推奨事項)
|
|
21
|
+
6. [実装ロードマップ](#実装ロードマップ)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 現在の実装分析
|
|
26
|
+
|
|
27
|
+
### ファイル構成
|
|
28
|
+
|
|
29
|
+
- **実装ファイル**: `lib/rufio/file_preview.rb`
|
|
30
|
+
- **主要クラス**: `Rufio::FilePreview`
|
|
31
|
+
- **コード行数**: 約200行
|
|
32
|
+
|
|
33
|
+
### 処理フロー
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
1. ファイル存在・読み取り権限チェック
|
|
37
|
+
2. バイナリファイル検出(先頭512バイトをサンプリング)
|
|
38
|
+
3. テキストファイル読み込み
|
|
39
|
+
- UTF-8でオープン
|
|
40
|
+
- 行ごとに読み込み(max_lines制限付き)
|
|
41
|
+
- 長い行の切り詰め(500文字超)
|
|
42
|
+
- chomp処理
|
|
43
|
+
4. ファイルタイプ判定(拡張子ベース)
|
|
44
|
+
5. メタデータ収集(サイズ、更新日時)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 現在のコードの特徴
|
|
48
|
+
|
|
49
|
+
**長所:**
|
|
50
|
+
- ✓ シンプルで保守しやすい
|
|
51
|
+
- ✓ エンコーディング処理が堅牢(UTF-8 → Shift_JIS フォールバック)
|
|
52
|
+
- ✓ 長い行の安全な処理
|
|
53
|
+
- ✓ バイナリファイルの適切な検出
|
|
54
|
+
|
|
55
|
+
**短所:**
|
|
56
|
+
- ✗ 行ごとの処理でオーバーヘッド
|
|
57
|
+
- ✗ chomp の繰り返し呼び出し
|
|
58
|
+
- ✗ エンコーディングエラー時の2回読み込み
|
|
59
|
+
- ✗ バイナリ検出のバイト配列走査
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## パフォーマンスベンチマーク結果
|
|
64
|
+
|
|
65
|
+
### テスト環境
|
|
66
|
+
|
|
67
|
+
- **プラットフォーム**: macOS (Apple Silicon)
|
|
68
|
+
- **Ruby バージョン**: 3.4.2
|
|
69
|
+
- **テスト日時**: 2026-01-03
|
|
70
|
+
|
|
71
|
+
### ベンチマーク1: max_lines パラメータの影響
|
|
72
|
+
|
|
73
|
+
| max_lines | 処理時間 (ms) | 1行あたり (µs) | ベースライン比 |
|
|
74
|
+
|-----------|---------------|----------------|----------------|
|
|
75
|
+
| 50 | 0.056 | 1.12 | 1.0x |
|
|
76
|
+
| 100 | 0.080 | 0.80 | 1.4x |
|
|
77
|
+
| 500 | 0.123 | 0.25 | 2.2x |
|
|
78
|
+
| 1,000 | 0.193 | 0.19 | 3.4x |
|
|
79
|
+
| 5,000 | 0.720 | 0.14 | 12.9x |
|
|
80
|
+
| 10,000 | 1.378 | 0.14 | 24.6x |
|
|
81
|
+
|
|
82
|
+
**観察:**
|
|
83
|
+
- 行数増加に対して**ほぼ線形**にスケール
|
|
84
|
+
- 1行あたりの処理時間は行数が増えると改善(キャッシング効果)
|
|
85
|
+
- 10,000行でも**予想より87%高速**(優れたスケーラビリティ)
|
|
86
|
+
|
|
87
|
+
### ベンチマーク2: ファイルタイプ別の性能
|
|
88
|
+
|
|
89
|
+
| ファイルタイプ | サイズ | 処理時間 (ms) | 備考 |
|
|
90
|
+
|----------------|-----------|---------------|-------------------------|
|
|
91
|
+
| Gemfile | 0.2 KB | 0.035 | 最速(小さいファイル) |
|
|
92
|
+
| Plain text | 4,882.8 KB| 0.049 | 大規模でも高速 |
|
|
93
|
+
| Ruby code | 108.4 KB | 0.060 | 通常のコードファイル |
|
|
94
|
+
| Markdown | 26.5 KB | 0.063 | ドキュメント |
|
|
95
|
+
| Real Ruby file | 1.6 KB | 0.064 | 実際のプロジェクトファイル|
|
|
96
|
+
|
|
97
|
+
**観察:**
|
|
98
|
+
- ファイルサイズより**ファイル構造**が影響
|
|
99
|
+
- すべてのファイルタイプで**0.06ms前後**と高速
|
|
100
|
+
- max_lines=50制限により、大規模ファイルでも高速
|
|
101
|
+
|
|
102
|
+
### ベンチマーク3: 処理内訳(max_lines=1000)
|
|
103
|
+
|
|
104
|
+
| 処理ステップ | 時間 (ms) | 割合 |
|
|
105
|
+
|----------------------|-----------|--------|
|
|
106
|
+
| テキストファイル読込 | 0.153 | 79.3% |
|
|
107
|
+
| バイナリ検出 | 0.015 | 7.8% |
|
|
108
|
+
| その他 | 0.025 | 12.7% |
|
|
109
|
+
| ファイルタイプ判定 | 0.000 | 0.2% |
|
|
110
|
+
| **合計** | **0.193** | 100% |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## ボトルネック特定
|
|
115
|
+
|
|
116
|
+
### 主要ボトルネック
|
|
117
|
+
|
|
118
|
+
#### 1. テキストファイル読み込み(79.3%)
|
|
119
|
+
|
|
120
|
+
**現在の実装:**
|
|
121
|
+
```ruby
|
|
122
|
+
File.open(file_path, "r:UTF-8") do |file|
|
|
123
|
+
file.each_line.with_index do |line, index|
|
|
124
|
+
break if index >= max_lines
|
|
125
|
+
|
|
126
|
+
if line.length > MAX_LINE_LENGTH
|
|
127
|
+
line = line[0...MAX_LINE_LENGTH] + "..."
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
lines << line.chomp
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**問題点:**
|
|
136
|
+
- `each_line` - 行ごとのイテレーション(Ruby VM オーバーヘッド)
|
|
137
|
+
- `chomp` - 毎回の文字列操作
|
|
138
|
+
- `length` チェック - 毎行で実行
|
|
139
|
+
- 文字列スライシング - メモリアロケーション
|
|
140
|
+
|
|
141
|
+
**改善の余地:** ⭐⭐⭐⭐⭐ (最大)
|
|
142
|
+
|
|
143
|
+
#### 2. バイナリ検出(7.8%)
|
|
144
|
+
|
|
145
|
+
**現在の実装:**
|
|
146
|
+
```ruby
|
|
147
|
+
binary_chars = sample.bytes.count { |byte|
|
|
148
|
+
byte < PRINTABLE_CHAR_THRESHOLD &&
|
|
149
|
+
!allowed_control_chars.include?(byte)
|
|
150
|
+
}
|
|
151
|
+
(binary_chars.to_f / sample.bytes.length) > BINARY_THRESHOLD
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**問題点:**
|
|
155
|
+
- `bytes.count` - バイト配列全体を走査
|
|
156
|
+
- ブロック評価 - 各バイトでlambda評価
|
|
157
|
+
- `include?` - 配列検索(3要素)
|
|
158
|
+
|
|
159
|
+
**改善の余地:** ⭐⭐⭐ (中程度)
|
|
160
|
+
|
|
161
|
+
#### 3. その他のオーバーヘッド(12.7%)
|
|
162
|
+
|
|
163
|
+
- ファイルオープン/クローズ
|
|
164
|
+
- Hash構築
|
|
165
|
+
- メタデータ取得
|
|
166
|
+
|
|
167
|
+
**改善の余地:** ⭐⭐ (小)
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 改善方式の比較
|
|
172
|
+
|
|
173
|
+
### 方式1: Zig ネイティブ実装
|
|
174
|
+
|
|
175
|
+
#### 概要
|
|
176
|
+
Zigでネイティブ拡張を実装し、ファイルI/Oとバイト処理を最適化。
|
|
177
|
+
|
|
178
|
+
#### 実装アプローチ
|
|
179
|
+
```zig
|
|
180
|
+
// 擬似コード
|
|
181
|
+
fn previewFile(path: []const u8, max_lines: usize) callconv(.c) c.VALUE {
|
|
182
|
+
// 1. mmap または buffered read でファイル読み込み
|
|
183
|
+
const file_content = readFileBuffered(path);
|
|
184
|
+
|
|
185
|
+
// 2. バイナリ検出(SIMD最適化可能)
|
|
186
|
+
if (isBinaryFast(file_content)) {
|
|
187
|
+
return createBinaryResponse();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 3. 行分割(memchrを使用)
|
|
191
|
+
var lines = ArrayList([]const u8).init(allocator);
|
|
192
|
+
var line_count: usize = 0;
|
|
193
|
+
var iter = std.mem.split(u8, file_content, "\n");
|
|
194
|
+
|
|
195
|
+
while (iter.next()) |line| {
|
|
196
|
+
if (line_count >= max_lines) break;
|
|
197
|
+
|
|
198
|
+
// 長い行の処理
|
|
199
|
+
const truncated = if (line.len > MAX_LINE_LENGTH)
|
|
200
|
+
line[0..MAX_LINE_LENGTH]
|
|
201
|
+
else
|
|
202
|
+
line;
|
|
203
|
+
|
|
204
|
+
lines.append(truncated);
|
|
205
|
+
line_count += 1;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 4. Ruby配列を構築
|
|
209
|
+
return createRubyArray(lines);
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### 予想性能改善
|
|
214
|
+
|
|
215
|
+
| max_lines | 現在 (ms) | Zig予想 (ms) | 改善率 |
|
|
216
|
+
|-----------|-----------|--------------|--------|
|
|
217
|
+
| 50 | 0.056 | 0.025 | 2.2x |
|
|
218
|
+
| 1,000 | 0.193 | 0.070 | 2.8x |
|
|
219
|
+
| 10,000 | 1.378 | 0.500 | 2.8x |
|
|
220
|
+
|
|
221
|
+
**根拠:**
|
|
222
|
+
- NativeScanner での実績(Zigは他の実装と同等)
|
|
223
|
+
- ファイルI/O最適化(buffered read, mmap)
|
|
224
|
+
- メモリアロケーション削減
|
|
225
|
+
- chomp/文字列操作の最適化
|
|
226
|
+
|
|
227
|
+
#### メリット
|
|
228
|
+
|
|
229
|
+
✓ **2-3倍の高速化**が期待できる
|
|
230
|
+
✓ **バイナリサイズ小** (52.6 KB - NativeScannerの実績)
|
|
231
|
+
✓ **FFI不要** - Ruby C API直接使用
|
|
232
|
+
✓ メモリ効率が良い
|
|
233
|
+
✓ C言語エコシステムと互換性
|
|
234
|
+
|
|
235
|
+
#### デメリット
|
|
236
|
+
|
|
237
|
+
✗ Zigの知識が必要
|
|
238
|
+
✗ ビルドプロセスが複雑化
|
|
239
|
+
✗ デバッグが難しい
|
|
240
|
+
✗ クロスプラットフォーム対応が必要
|
|
241
|
+
|
|
242
|
+
#### 実装工数
|
|
243
|
+
|
|
244
|
+
- **開発**: 2-3日
|
|
245
|
+
- **テスト**: 1日
|
|
246
|
+
- **ドキュメント**: 0.5日
|
|
247
|
+
- **合計**: 約3.5-4.5日
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### 方式2: Rust (Magnus) ネイティブ実装
|
|
252
|
+
|
|
253
|
+
#### 概要
|
|
254
|
+
Rustとmagnusクレートでネイティブ拡張を実装。
|
|
255
|
+
|
|
256
|
+
#### 実装アプローチ
|
|
257
|
+
```rust
|
|
258
|
+
// 擬似コード
|
|
259
|
+
fn preview_file(ruby: &Ruby, path: String, max_lines: usize) -> Result<Value, Error> {
|
|
260
|
+
// BufReaderで効率的な読み込み
|
|
261
|
+
let file = File::open(&path)?;
|
|
262
|
+
let reader = BufReader::new(file);
|
|
263
|
+
|
|
264
|
+
// バイナリ検出
|
|
265
|
+
if is_binary(&path)? {
|
|
266
|
+
return create_binary_response(ruby);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let mut lines = Vec::with_capacity(max_lines);
|
|
270
|
+
for (i, line) in reader.lines().enumerate() {
|
|
271
|
+
if i >= max_lines { break; }
|
|
272
|
+
|
|
273
|
+
let line = line?;
|
|
274
|
+
let truncated = if line.len() > MAX_LINE_LENGTH {
|
|
275
|
+
format!("{}...", &line[..MAX_LINE_LENGTH])
|
|
276
|
+
} else {
|
|
277
|
+
line
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
lines.push(truncated);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
create_ruby_array(ruby, lines)
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
#### 予想性能改善
|
|
288
|
+
|
|
289
|
+
| max_lines | 現在 (ms) | Rust予想 (ms) | 改善率 |
|
|
290
|
+
|-----------|-----------|---------------|--------|
|
|
291
|
+
| 50 | 0.056 | 0.025 | 2.2x |
|
|
292
|
+
| 1,000 | 0.193 | 0.070 | 2.8x |
|
|
293
|
+
| 10,000 | 1.378 | 0.500 | 2.8x |
|
|
294
|
+
|
|
295
|
+
**Zigと同等の性能を想定**
|
|
296
|
+
|
|
297
|
+
#### メリット
|
|
298
|
+
|
|
299
|
+
✓ 2-3倍の高速化
|
|
300
|
+
✓ 型安全性が高い
|
|
301
|
+
✓ エラーハンドリングが堅牢
|
|
302
|
+
✓ Rustエコシステムの活用
|
|
303
|
+
|
|
304
|
+
#### デメリット
|
|
305
|
+
|
|
306
|
+
✗ **バイナリサイズ大** (314.1 KB - NativeScannerの実績)
|
|
307
|
+
✗ コンパイル時間が長い
|
|
308
|
+
✗ Rustの学習コスト
|
|
309
|
+
✗ Zigより約6倍大きいバイナリ
|
|
310
|
+
|
|
311
|
+
#### 実装工数
|
|
312
|
+
|
|
313
|
+
- **開発**: 2-3日
|
|
314
|
+
- **テスト**: 1日
|
|
315
|
+
- **ドキュメント**: 0.5日
|
|
316
|
+
- **合計**: 約3.5-4.5日
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
### 方式3: Pure Ruby 最適化
|
|
321
|
+
|
|
322
|
+
#### 概要
|
|
323
|
+
現在のRuby実装をアルゴリズムレベルで最適化。
|
|
324
|
+
|
|
325
|
+
#### 実装アプローチ
|
|
326
|
+
|
|
327
|
+
**最適化1: IO.readlines使用**
|
|
328
|
+
```ruby
|
|
329
|
+
def read_text_file_optimized(file_path, max_lines)
|
|
330
|
+
# 一度に読み込んでから処理
|
|
331
|
+
lines = IO.readlines(file_path, chomp: true, encoding: 'UTF-8')
|
|
332
|
+
.first(max_lines)
|
|
333
|
+
.map { |line|
|
|
334
|
+
line.length > MAX_LINE_LENGTH ? line[0...MAX_LINE_LENGTH] + "..." : line
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
{
|
|
338
|
+
content: lines,
|
|
339
|
+
truncated: IO.readlines(file_path).size > max_lines,
|
|
340
|
+
encoding: "UTF-8"
|
|
341
|
+
}
|
|
342
|
+
end
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**最適化2: バイナリ検出の改善**
|
|
346
|
+
```ruby
|
|
347
|
+
def binary_file_optimized?(sample)
|
|
348
|
+
return false if sample.empty?
|
|
349
|
+
|
|
350
|
+
# Set使用で高速化
|
|
351
|
+
allowed = Set.new([9, 10, 13])
|
|
352
|
+
binary_count = 0
|
|
353
|
+
|
|
354
|
+
sample.each_byte do |byte|
|
|
355
|
+
binary_count += 1 if byte < 32 && !allowed.include?(byte)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
(binary_count.to_f / sample.bytesize) > BINARY_THRESHOLD
|
|
359
|
+
end
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**最適化3: 早期リターン**
|
|
363
|
+
```ruby
|
|
364
|
+
def preview_file_optimized(file_path, max_lines: DEFAULT_MAX_LINES)
|
|
365
|
+
# stat一度だけ呼ぶ
|
|
366
|
+
stat = File.stat(file_path)
|
|
367
|
+
return empty_response if stat.size == 0
|
|
368
|
+
|
|
369
|
+
# ... 以下同様
|
|
370
|
+
end
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
#### 予想性能改善
|
|
374
|
+
|
|
375
|
+
| max_lines | 現在 (ms) | 最適化後 (ms) | 改善率 |
|
|
376
|
+
|-----------|-----------|---------------|--------|
|
|
377
|
+
| 50 | 0.056 | 0.050 | 1.1x |
|
|
378
|
+
| 1,000 | 0.193 | 0.155 | 1.2x |
|
|
379
|
+
| 10,000 | 1.378 | 1.100 | 1.3x |
|
|
380
|
+
|
|
381
|
+
**10-25%の改善を想定**
|
|
382
|
+
|
|
383
|
+
#### メリット
|
|
384
|
+
|
|
385
|
+
✓ **最小限の変更**で改善
|
|
386
|
+
✓ **保守性**を維持
|
|
387
|
+
✓ デプロイが簡単
|
|
388
|
+
✓ デバッグが容易
|
|
389
|
+
✓ クロスプラットフォーム対応不要
|
|
390
|
+
|
|
391
|
+
#### デメリット
|
|
392
|
+
|
|
393
|
+
✗ 改善幅が限定的(10-25%)
|
|
394
|
+
✗ ネイティブ実装には及ばない
|
|
395
|
+
✗ 大規模ファイルで依然として遅い
|
|
396
|
+
|
|
397
|
+
#### 実装工数
|
|
398
|
+
|
|
399
|
+
- **開発**: 0.5-1日
|
|
400
|
+
- **テスト**: 0.5日
|
|
401
|
+
- **ドキュメント**: 0.5日
|
|
402
|
+
- **合計**: 約1.5-2日
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
### 方式4: YJIT 有効化
|
|
407
|
+
|
|
408
|
+
#### 概要
|
|
409
|
+
Ruby 3.4のYJIT(Just-In-Timeコンパイラ)を活用。
|
|
410
|
+
|
|
411
|
+
#### 実装アプローチ
|
|
412
|
+
```bash
|
|
413
|
+
# rufioの起動時にYJITを有効化
|
|
414
|
+
ruby --yjit bin/rufio
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
または、コード内で有効化:
|
|
418
|
+
```ruby
|
|
419
|
+
# lib/rufio.rb
|
|
420
|
+
if defined?(RubyVM::YJIT)
|
|
421
|
+
RubyVM::YJIT.enable
|
|
422
|
+
end
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
#### 予想性能改善
|
|
426
|
+
|
|
427
|
+
| max_lines | 現在 (ms) | YJIT (ms) | 改善率 |
|
|
428
|
+
|-----------|-----------|-----------|--------|
|
|
429
|
+
| 50 | 0.056 | 0.053 | 1.06x |
|
|
430
|
+
| 1,000 | 0.193 | 0.174 | 1.11x |
|
|
431
|
+
| 10,000 | 1.378 | 1.240 | 1.11x |
|
|
432
|
+
|
|
433
|
+
**5-11%の改善を想定**
|
|
434
|
+
|
|
435
|
+
#### メリット
|
|
436
|
+
|
|
437
|
+
✓ **コード変更不要**
|
|
438
|
+
✓ すぐに試せる
|
|
439
|
+
✓ アプリケーション全体が高速化
|
|
440
|
+
✓ 標準Ruby機能
|
|
441
|
+
|
|
442
|
+
#### デメリット
|
|
443
|
+
|
|
444
|
+
✗ 改善幅が小さい(5-11%)
|
|
445
|
+
✗ メモリ使用量増加(~40MB)
|
|
446
|
+
✗ ウォームアップ時間が必要
|
|
447
|
+
✗ ネイティブ実装には及ばない
|
|
448
|
+
|
|
449
|
+
#### 実装工数
|
|
450
|
+
|
|
451
|
+
- **開発**: 0.1日(設定のみ)
|
|
452
|
+
- **テスト**: 0.5日
|
|
453
|
+
- **ドキュメント**: 0.5日
|
|
454
|
+
- **合計**: 約1日
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
### 方式5: Rust (FFI) 実装
|
|
459
|
+
|
|
460
|
+
#### 概要
|
|
461
|
+
RustライブラリをFFI経由で呼び出し。
|
|
462
|
+
|
|
463
|
+
#### 予想性能改善
|
|
464
|
+
|
|
465
|
+
NativeScannerの経験から、Rust FFIはMagnus/Zigより**やや遅い**可能性があります(JSON シリアライゼーションのオーバーヘッド)。
|
|
466
|
+
|
|
467
|
+
| max_lines | 現在 (ms) | Rust FFI (ms) | 改善率 |
|
|
468
|
+
|-----------|-----------|---------------|--------|
|
|
469
|
+
| 50 | 0.056 | 0.030 | 1.9x |
|
|
470
|
+
| 1,000 | 0.193 | 0.085 | 2.3x |
|
|
471
|
+
| 10,000 | 1.378 | 0.600 | 2.3x |
|
|
472
|
+
|
|
473
|
+
**2-2.5倍の改善を想定(Magnusよりやや劣る)**
|
|
474
|
+
|
|
475
|
+
#### メリット
|
|
476
|
+
|
|
477
|
+
✓ 2倍程度の高速化
|
|
478
|
+
✓ 既存のRust FFI インフラ活用
|
|
479
|
+
|
|
480
|
+
#### デメリット
|
|
481
|
+
|
|
482
|
+
✗ FFIオーバーヘッド
|
|
483
|
+
✗ JSONシリアライゼーションコスト
|
|
484
|
+
✗ Magnus/Zigより遅い
|
|
485
|
+
✗ 複雑性増加
|
|
486
|
+
|
|
487
|
+
#### 実装工数
|
|
488
|
+
|
|
489
|
+
- **開発**: 2-3日
|
|
490
|
+
- **テスト**: 1日
|
|
491
|
+
- **ドキュメント**: 0.5日
|
|
492
|
+
- **合計**: 約3.5-4.5日
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## 改善方式の総合比較
|
|
497
|
+
|
|
498
|
+
### 性能比較(max_lines=1000の場合)
|
|
499
|
+
|
|
500
|
+
| 方式 | 処理時間 | 改善率 | バイナリサイズ | 工数 | 保守性 |
|
|
501
|
+
|-------------------------|----------|--------|----------------|--------|--------|
|
|
502
|
+
| **現在の実装** | 0.193ms | - | - | - | ⭐⭐⭐⭐⭐ |
|
|
503
|
+
| **Pure Ruby 最適化** | 0.155ms | 1.2x | - | 1.5日 | ⭐⭐⭐⭐⭐ |
|
|
504
|
+
| **YJIT** | 0.174ms | 1.1x | - | 1日 | ⭐⭐⭐⭐⭐ |
|
|
505
|
+
| **Rust (FFI)** | 0.085ms | 2.3x | - | 3.5日 | ⭐⭐⭐ |
|
|
506
|
+
| **Rust (Magnus)** | 0.070ms | 2.8x | 314 KB | 3.5日 | ⭐⭐⭐ |
|
|
507
|
+
| **Zig** | 0.070ms | 2.8x | 53 KB | 3.5日 | ⭐⭐⭐ |
|
|
508
|
+
|
|
509
|
+
### コストパフォーマンス分析
|
|
510
|
+
|
|
511
|
+
| 方式 | 改善率 | 工数 | CPP (改善率/日) | 推奨度 |
|
|
512
|
+
|-------------------------|--------|-------|-----------------|--------|
|
|
513
|
+
| **YJIT** | 1.1x | 1日 | 0.11 | ⭐⭐⭐ |
|
|
514
|
+
| **Pure Ruby 最適化** | 1.2x | 1.5日 | 0.13 | ⭐⭐⭐⭐ |
|
|
515
|
+
| **Rust (FFI)** | 2.3x | 3.5日 | 0.37 | ⭐⭐ |
|
|
516
|
+
| **Rust (Magnus)** | 2.8x | 3.5日 | 0.51 | ⭐⭐⭐ |
|
|
517
|
+
| **Zig** | 2.8x | 3.5日 | 0.51 | ⭐⭐⭐⭐⭐ |
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
## 推奨事項
|
|
522
|
+
|
|
523
|
+
### 即座に実施すべき改善(Phase 1)
|
|
524
|
+
|
|
525
|
+
#### 1. YJIT 有効化 ⭐⭐⭐⭐
|
|
526
|
+
|
|
527
|
+
**理由:**
|
|
528
|
+
- コスト: 最小(設定のみ)
|
|
529
|
+
- 効果: 全アプリケーションで5-11%改善
|
|
530
|
+
- リスク: 非常に低い
|
|
531
|
+
|
|
532
|
+
**実装:**
|
|
533
|
+
```ruby
|
|
534
|
+
# lib/rufio.rb の先頭に追加
|
|
535
|
+
if defined?(RubyVM::YJIT) && !RubyVM::YJIT.enabled?
|
|
536
|
+
RubyVM::YJIT.enable
|
|
537
|
+
end
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**期待効果:** 0.193ms → 0.174ms(10%改善)
|
|
541
|
+
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
#### 2. Pure Ruby 最適化 ⭐⭐⭐⭐⭐
|
|
545
|
+
|
|
546
|
+
**理由:**
|
|
547
|
+
- コスト: 低(1.5日)
|
|
548
|
+
- 効果: 10-25%改善
|
|
549
|
+
- リスク: 低(既存コードベース)
|
|
550
|
+
- 保守性: 高
|
|
551
|
+
|
|
552
|
+
**優先実装項目:**
|
|
553
|
+
|
|
554
|
+
**a) バイナリ検出の最適化**
|
|
555
|
+
```ruby
|
|
556
|
+
ALLOWED_CONTROL_CHARS = Set.new([9, 10, 13]).freeze
|
|
557
|
+
|
|
558
|
+
def binary_file?(sample)
|
|
559
|
+
return false if sample.empty?
|
|
560
|
+
|
|
561
|
+
binary_count = sample.each_byte.count { |b|
|
|
562
|
+
b < 32 && !ALLOWED_CONTROL_CHARS.include?(b)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
(binary_count.to_f / sample.bytesize) > BINARY_THRESHOLD
|
|
566
|
+
end
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
**b) ファイル読み込みの最適化**
|
|
570
|
+
```ruby
|
|
571
|
+
def read_text_file(file_path, max_lines)
|
|
572
|
+
content = File.read(file_path, encoding: 'UTF-8')
|
|
573
|
+
lines = content.lines(chomp: true).first(max_lines)
|
|
574
|
+
|
|
575
|
+
# 長い行の処理
|
|
576
|
+
lines.map! { |line|
|
|
577
|
+
line.length > MAX_LINE_LENGTH ? "#{line[0...MAX_LINE_LENGTH]}..." : line
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
{
|
|
581
|
+
content: lines,
|
|
582
|
+
truncated: content.count("\n") > max_lines,
|
|
583
|
+
encoding: 'UTF-8'
|
|
584
|
+
}
|
|
585
|
+
rescue Encoding::InvalidByteSequenceError
|
|
586
|
+
# Shift_JIS fallback
|
|
587
|
+
fallback_read(file_path, max_lines)
|
|
588
|
+
end
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
**期待効果:** 0.193ms → 0.155ms(20%改善)
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
### 中長期的な改善(Phase 2)
|
|
596
|
+
|
|
597
|
+
#### 3. Zig ネイティブ実装 ⭐⭐⭐⭐⭐
|
|
598
|
+
|
|
599
|
+
**理由:**
|
|
600
|
+
- 最大の性能改善(2.8倍)
|
|
601
|
+
- 最小のバイナリサイズ(53 KB)
|
|
602
|
+
- NativeScannerでの実績あり
|
|
603
|
+
- コストパフォーマンス最高
|
|
604
|
+
|
|
605
|
+
**実装タイミング:**
|
|
606
|
+
- Phase 1完了後
|
|
607
|
+
- パフォーマンスがまだ不足している場合
|
|
608
|
+
- 大規模ファイルの頻繁な閲覧がユースケースに含まれる場合
|
|
609
|
+
|
|
610
|
+
**実装ロードマップ:**
|
|
611
|
+
1. NativeScannerの実装パターンを踏襲
|
|
612
|
+
2. FilePreviewZig クラスとして実装
|
|
613
|
+
3. 既存のFilePreviewとの互換性を維持
|
|
614
|
+
4. モード切り替え可能にする('zig', 'ruby')
|
|
615
|
+
|
|
616
|
+
**期待効果:** 0.193ms → 0.070ms(2.8倍高速化)
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
620
|
+
### 推奨しない方式
|
|
621
|
+
|
|
622
|
+
#### Rust (FFI)
|
|
623
|
+
- Zigと同等の工数だが、性能はやや劣る
|
|
624
|
+
- FFIオーバーヘッドが不要
|
|
625
|
+
- 既にZigの実績がある
|
|
626
|
+
|
|
627
|
+
#### Rust (Magnus)
|
|
628
|
+
- Zigと同等の性能だが、バイナリが約6倍大きい
|
|
629
|
+
- 特別な理由がない限りZigを推奨
|
|
630
|
+
|
|
631
|
+
---
|
|
632
|
+
|
|
633
|
+
## 実装ロードマップ
|
|
634
|
+
|
|
635
|
+
### フェーズ1: 即効性の高い改善(推奨)
|
|
636
|
+
|
|
637
|
+
**目標:** 20-30%の性能改善を1週間以内に達成
|
|
638
|
+
|
|
639
|
+
| タスク | 工数 | 担当 | 期待効果 |
|
|
640
|
+
|--------|------|------|----------|
|
|
641
|
+
| YJIT有効化 | 0.5日 | Backend | +10% |
|
|
642
|
+
| Pure Ruby最適化 - バイナリ検出 | 0.5日 | Backend | +5% |
|
|
643
|
+
| Pure Ruby最適化 - ファイル読み込み | 1日 | Backend | +15% |
|
|
644
|
+
| テスト・ベンチマーク | 0.5日 | QA | - |
|
|
645
|
+
| ドキュメント更新 | 0.5日 | Backend | - |
|
|
646
|
+
| **合計** | **3日** | | **+30%** |
|
|
647
|
+
|
|
648
|
+
**成果物:**
|
|
649
|
+
- 最適化されたFilePreviewクラス
|
|
650
|
+
- ベンチマーク結果レポート
|
|
651
|
+
- 更新されたドキュメント
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
### フェーズ2: ネイティブ実装(オプション)
|
|
656
|
+
|
|
657
|
+
**目標:** 2-3倍の性能改善
|
|
658
|
+
|
|
659
|
+
**前提条件:**
|
|
660
|
+
- フェーズ1が完了していること
|
|
661
|
+
- 性能要件がまだ満たされていないこと
|
|
662
|
+
- 大規模ファイルの頻繁な閲覧が必要
|
|
663
|
+
|
|
664
|
+
| タスク | 工数 | 担当 | 期待効果 |
|
|
665
|
+
|--------|------|------|----------|
|
|
666
|
+
| Zig実装 - コア機能 | 2日 | Backend | - |
|
|
667
|
+
| Zig実装 - Ruby統合 | 1日 | Backend | - |
|
|
668
|
+
| テスト(単体・統合) | 1日 | QA | - |
|
|
669
|
+
| ベンチマーク・検証 | 0.5日 | Backend | - |
|
|
670
|
+
| ドキュメント | 0.5日 | Backend | - |
|
|
671
|
+
| **合計** | **5日** | | **+180%** |
|
|
672
|
+
|
|
673
|
+
**成果物:**
|
|
674
|
+
- FilePreviewZig ネイティブ拡張
|
|
675
|
+
- フォールバック機構
|
|
676
|
+
- 包括的なテストスイート
|
|
677
|
+
- パフォーマンスベンチマーク
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
## 付録
|
|
682
|
+
|
|
683
|
+
### A. ベンチマーク詳細データ
|
|
684
|
+
|
|
685
|
+
#### テスト環境
|
|
686
|
+
```
|
|
687
|
+
OS: macOS 14.x (Apple Silicon)
|
|
688
|
+
CPU: Apple M1/M2
|
|
689
|
+
RAM: 16GB
|
|
690
|
+
Ruby: 3.4.2
|
|
691
|
+
Zig: 0.15.2 (フェーズ2実装時)
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
#### テストファイル
|
|
695
|
+
```
|
|
696
|
+
small.txt: 1KB (50行)
|
|
697
|
+
medium.txt: 100KB (1,000行)
|
|
698
|
+
large.txt: 1MB (10,000行)
|
|
699
|
+
huge.txt: 10MB (100,000行)
|
|
700
|
+
long_lines.txt: 長い行 (10,000文字/行)
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
### B. 実装リファレンス
|
|
704
|
+
|
|
705
|
+
#### 参考コード
|
|
706
|
+
- `lib/rufio/native_scanner.rb` - FFI実装パターン
|
|
707
|
+
- `lib/rufio/native_scanner_zig.rb` - Zig統合パターン
|
|
708
|
+
- `lib_zig/rufio_native/src/main.zig` - Zig実装例
|
|
709
|
+
|
|
710
|
+
#### 関連ドキュメント
|
|
711
|
+
- [YJIT_BENCHMARK_RESULTS.md](../directory-scanner-test/YJIT_BENCHMARK_RESULTS.md)
|
|
712
|
+
- [BENCHMARK_RESULTS.md](../directory-scanner-test/BENCHMARK_RESULTS.md)
|
|
713
|
+
|
|
714
|
+
### C. リスク分析
|
|
715
|
+
|
|
716
|
+
| リスク | 確率 | 影響度 | 軽減策 |
|
|
717
|
+
|--------|------|--------|--------|
|
|
718
|
+
| Ruby最適化で互換性問題 | 低 | 中 | 包括的なテスト |
|
|
719
|
+
| Zig実装でクロスプラットフォーム問題 | 中 | 高 | CIでマルチOS検証 |
|
|
720
|
+
| YJITでメモリ不足 | 低 | 中 | メモリ使用量監視 |
|
|
721
|
+
| 性能改善が期待以下 | 中 | 低 | フォールバック維持 |
|
|
722
|
+
|
|
723
|
+
---
|
|
724
|
+
|
|
725
|
+
## 結論
|
|
726
|
+
|
|
727
|
+
### 推奨戦略: 段階的改善アプローチ
|
|
728
|
+
|
|
729
|
+
**即座の対応(フェーズ1):**
|
|
730
|
+
1. **YJIT有効化** - 最小コストで10%改善
|
|
731
|
+
2. **Pure Ruby最適化** - 1.5日で20%改善
|
|
732
|
+
3. **合計30%改善** を3日で達成
|
|
733
|
+
|
|
734
|
+
**将来の対応(フェーズ2、必要に応じて):**
|
|
735
|
+
1. **Zig ネイティブ実装** - 5日で2.8倍高速化
|
|
736
|
+
2. 大規模ファイルの頻繁な閲覧が必要になった場合に実施
|
|
737
|
+
|
|
738
|
+
### 期待される成果
|
|
739
|
+
|
|
740
|
+
#### フェーズ1完了時
|
|
741
|
+
```
|
|
742
|
+
小規模ファイル (50行): 0.056ms → 0.039ms (1.4x高速化)
|
|
743
|
+
中規模ファイル (1000行): 0.193ms → 0.135ms (1.4x高速化)
|
|
744
|
+
大規模ファイル (10000行): 1.378ms → 0.965ms (1.4x高速化)
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
#### フェーズ2完了時(オプション)
|
|
748
|
+
```
|
|
749
|
+
小規模ファイル (50行): 0.056ms → 0.025ms (2.2x高速化)
|
|
750
|
+
中規模ファイル (1000行): 0.193ms → 0.070ms (2.8x高速化)
|
|
751
|
+
大規模ファイル (10000行): 1.378ms → 0.500ms (2.8x高速化)
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
---
|
|
755
|
+
|
|
756
|
+
**レポート作成日:** 2026-01-03
|
|
757
|
+
**作成者:** Claude Sonnet 4.5
|
|
758
|
+
**バージョン:** 1.0
|
|
759
|
+
**ステータス:** 最終版
|