1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rustで並列ファイル転送を実装する——Semaphoreで同時実行数を制御してAndroid同期を高速化

1
Posted at

8年前のMacBook Air(Intel)で全テスト済み。7本のMacアプリをソロ開発した実体験から書いています。案件でも広告でもありません。

順次ファイル転送は遅い。HiyokoAutoSyncは同時実行数を制限した並列転送を使っている。その実装を紹介する。


順次転送の問題

Androidから100枚の写真を1枚ずつMacに転送すると、前の転送が終わるまで次が待たされる。ファイルごとのADBオーバーヘッドが積み重なる。大量のライブラリだと数分かかる。

並列転送は利用可能な帯域をより効率的に使える。同じ100ファイルを6並列で転送すると、体感できるほど速くなる。


tokio::sync::Semaphoreで同時実行数を制御

無制限の並列化はむしろ逆効果——ADB接続とデバイスを圧迫する。セマフォで同時転送数を適切な数に制限する:

use tokio::sync::Semaphore;
use std::sync::Arc;

const MAX_CONCURRENT: usize = 6;

async fn transfer_files(files: Vec<FileEntry>) -> Result<(), AppError> {
    let semaphore = Arc::new(Semaphore::new(MAX_CONCURRENT));
    let mut handles = vec![];

    for file in files {
        let sem = Arc::clone(&semaphore);
        let handle = tokio::spawn(async move {
            let _permit = sem.acquire().await.unwrap();
            transfer_single_file(&file).await
        });
        handles.push(handle);
    }

    // 結果を収集
    for handle in handles {
        handle.await??;
    }

    Ok(())
}

テストした結果、6並列がスイートスポットだった——接続を圧迫せずに速い。


並列転送のプログレス追跡

各転送が独立して進捗を報告する必要がある。アトミックカウンターを使う:

use std::sync::atomic::{AtomicUsize, Ordering};

let completed = Arc::new(AtomicUsize::new(0));
let total = files.len();

for file in files {
    let completed = Arc::clone(&completed);
    let handle_clone = app_handle.clone();

    tokio::spawn(async move {
        let _permit = sem.acquire().await.unwrap();
        transfer_single_file(&file).await?;

        let done = completed.fetch_add(1, Ordering::Relaxed) + 1;
        handle_clone.emit("transfer-progress", Progress {
            completed: done,
            total,
            current_file: file.name.clone(),
        }).ok();

        Ok::<(), AppError>(())
    });
}

並列処理のエラーハンドリング

1つの転送失敗が他の全てを止めてはいけない。エラーを収集して最後にまとめて報告する:

let results: Vec<Result<(), AppError>> = futures::future::join_all(handles)
    .await
    .into_iter()
    .map(|r| r.unwrap_or_else(|e| Err(AppError::Task(e.to_string()))))
    .collect();

let errors: Vec<_> = results.into_iter().filter_map(|r| r.err()).collect();
if !errors.is_empty() {
    // 部分的な失敗を報告——一部転送済み、一部未転送
}

ファイル転送では「全か無か」より部分的な成功の方がいい。


結果

HiyokoAutoSyncの6並列転送は、大量の写真ライブラリに対して順次転送より体感できるほど速い。セマフォパターンは同時実行数を制限したい並列処理全般に再利用できる。


記事が参考になったら ❤️ もらえると励みになります!

HiyokoAutoSync | X → @hiyoyok

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?