3
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とWASI 0.3で実現する非同期I/O:組み込みシステム向けパフォーマンス最適化の実践ガイド

3
Last updated at Posted at 2026-02-28

この記事でわかること

  • WASI 0.3で導入されたネイティブ非同期I/Oの仕組みと、従来のpollableベース手動ポーリングとの違い
  • Rustでwasm32-wasip2ターゲットを使い、非同期I/O処理を組み込みシステム向けに実装する方法
  • Wasmtime AOTコンパイルによるネイティブC++比93%の性能を達成するための最適化テクニック
  • 組み込みIoTデバイス(ESP32、Raspberry Pi Pico等)でのWASMランタイム選定基準とベンチマーク結果
  • WASI 0.2から0.3への移行で得られるパフォーマンス改善と、移行時の注意点

対象読者

  • 想定読者: 中級〜上級のRustユーザーで、WebAssemblyや組み込みシステムに関心がある方
  • 必要な前提知識:
    • Rust 1.80以降の基礎文法とasync/awaitの基本的な理解
    • WebAssemblyの概念(WATやWASMバイナリの基本)
    • コマンドラインでのcargo操作

結論・成果

WASI 0.3のネイティブ非同期サポートにより、従来のWASI 0.2でのpollableベース手動ポーリングと比較して、I/O多重化のコード量が約40%削減されます。Wasmtime AOTコンパイルを適用すると、ファイルI/O操作でネイティブC++の93%の性能を達成でき、さらにWizerによるプリイニシャライズで起動時間が32%短縮されたと報告されています(dasroot.net 2026年1月の記事による)。一方で、マイクロコントローラ上でのWASM実行はネイティブコードと比較して10〜50倍の実行時間オーバーヘッドがあり、省電力が求められるバッテリー駆動デバイスでは慎重な検討が必要です。

WASI 0.3の非同期I/Oアーキテクチャを理解する

2026年2月、WASI 0.3.0がWasmtime 37以降で利用可能になりました。この更新の中核は、Component Modelへのネイティブ非同期サポートの導入です。従来のWASI 0.2では、非同期操作を実現するためにpollableリソースを使った手動ポーリングが必要でした。WASI 0.3では、WIT(WebAssembly Interface Types)レベルでasyncキーワードを直接使用でき、ランタイムがタスクスケジューリングを自動管理します。

WASI 0.2と0.3の非同期モデルの違い

WASI 0.2とWASI 0.3では、非同期I/Oのアプローチが根本的に異なります。以下の表で主要な違いを整理してみましょう。

項目 WASI 0.2 WASI 0.3
非同期モデル pollableリソース + poll-oneoff() ネイティブasync/await
ストリーム型 u8バイトストリームのみ ジェネリックstream<T>
Future Resourceパターンで模倣 ネイティブfuture<T>
バックプレッシャー アプリケーション層で手動実装 ランタイムによる自動管理
エラー伝播 エラーコードのみ error-contextによる構造化エラー
関数カラーリング async/sync境界で問題あり シームレスに接続可能

この表を見ると、WASI 0.3は非同期プログラミングにおける多くの摩擦を取り除いていることがわかります。

stream<T>future<T>型の導入

WASI 0.3で導入されたstream<T>future<T>は、Component Modelのファーストクラス型です。WASI 0.2ではstreamu8のバイトストリームに限定されていましたが、0.3ではジェネリック型として任意のデータ構造をストリーミングできます。

future<T>は線形所有権セマンティクスを持ち、一度しか消費できません。ランタイムがライフサイクルを自動管理するため、メモリリークのリスクが低減されます。

WIT定義の例を見てみましょう。

// WASI 0.3でのHTTPハンドラ定義例
package example:http-handler;

interface handler {
    use wasi:http/types.{incoming-request, response-outparam};

    // WASI 0.3: async関数として直接定義
    async func handle(request: incoming-request) -> result<incoming-response, error-code>;
}

WASI 0.2では同じ機能を実現するために、future-incoming-responseリソースを定義し、get()subscribe()メソッドを手動で呼び出す必要がありました。0.3ではこのボイラープレートが不要になります。

なぜこの設計が重要か:

  • 関数カラーリング問題の回避: 多くの言語ではasync関数とsync関数が別の「色」を持ち、境界で変換が必要です。WASI 0.3のComponent ModelではCanonical ABIレベルで非同期を処理するため、asyncインポートをsyncエクスポートの関数からシームレスに呼び出せます(WASI Standards Evolution Whitepaperによる)
  • バックプレッシャーの自動管理: stream<T>にはバックプレッシャー機構が組み込まれており、コンシューマのバッファが満杯になるとプロデューサの書き込みが自動的に中断されます

注意: WASI 0.3は2026年2月時点でPreviewステータスです。本番環境での利用前に、Wasmtimeのリリースノートで安定性を確認してください。WASI 0.2との後方互換性は維持されますが、一部のインターフェースは0.3で再設計されています。

Rustで非同期WASI I/Oを実装する

実際にRustでWASI 0.3の非同期I/Oを活用したコードを書いてみましょう。ここでは、WASI 0.2向けのwasi-async-runtimeクレートを使ったアプローチと、WASI 0.3のネイティブasyncアプローチの両方を紹介します。

開発環境のセットアップ

まず、必要なツールチェーンを準備します。

# Rustのwasm32-wasip2ターゲットを追加
rustup target add wasm32-wasip2

# Wasmtime 37以降をインストール(WASI 0.3プレビュー対応)
curl https://wasmtime.dev/install.sh -sSf | bash

# wasm-opt(バイナリ最適化ツール)をインストール
cargo install wasm-opt

# バージョン確認
rustc --version  # 1.80以降を推奨
wasmtime --version  # 37以降でWASI 0.3プレビュー対応

WASI 0.2向け非同期ランタイムの実装パターン

現時点でWASI 0.2をターゲットにする場合、wasi-async-runtimeクレート(v0.1.2)が利用できます。このランタイムは、Poller・Reactor・block_onの3層アーキテクチャで構成されています。

// Cargo.toml
// [dependencies]
// wasi-async-runtime = "0.1.2"
// wasi = "0.12.1"

use wasi_async_runtime::{block_on, Reactor};

// エントリポイント: block_onでイベントループを起動
fn main() {
    block_on(|reactor| async move {
        // reactorを通じて非同期操作を実行
        let result = async_file_read(&reactor, "config.json").await;
        match result {
            Ok(data) => println!("読み込み完了: {} bytes", data.len()),
            Err(e) => eprintln!("エラー: {}", e),
        }
    });
}

// WASI 0.2のpollableを使った非同期ファイル読み込み
async fn async_file_read(
    reactor: &Reactor,
    path: &str,
) -> Result<Vec<u8>, String> {
    use wasi::filesystem::types::{DescriptorFlags, OpenFlags, PathFlags};

    // 1. WASIファイルシステムAPIでファイルを開く
    let dirs = wasi::filesystem::preopens::get_directories();
    let (dir_fd, _) = dirs.first().ok_or("No preopened directory")?;

    let file = dir_fd
        .open_at(
            PathFlags::empty(),
            path,
            OpenFlags::empty(),
            DescriptorFlags::READ,
        )
        .map_err(|e| format!("open error: {:?}", e))?;

    // 2. ストリーム経由でpollableを取得してreactorに登録
    let stream = file.read_via_stream(0).map_err(|e| format!("stream error: {:?}", e))?;
    let pollable = stream.subscribe();
    reactor.wait_for(pollable).await;

    // 3. ファイルの内容を読み込み
    let stat = file.stat().map_err(|e| format!("stat error: {:?}", e))?;
    let data = file
        .read(stat.size, 0)
        .map_err(|e| format!("read error: {:?}", e))?;

    Ok(data.0)
}

このパターンでは、3つのステップが基本になります。

  1. WASI APIを呼び出してPollableを取得
  2. reactor.wait_for(pollable).awaitでポーリングを委譲
  3. 準備完了後に結果を取得

なぜwasi-async-runtimeを選んだか:

  • tokiowasm32-wasiターゲットで一部機能が制限される(ソケット作成の非対応など)
  • wasi-async-runtimeはWASI 0.2のreadiness-basedモデルに特化しており、シングルスレッド環境で最小限のオーバーヘッドで動作する
  • futures-concurrencyとの併用で(a, b).join().awaitパターンが使える

注意: wasi-async-runtimeはWASI 0.2向けであり、WASI 0.3のネイティブasyncとは異なるアプローチです。WASI 0.3が安定版になった場合は、ネイティブasyncへの移行を検討してください。

WASI 0.3向けネイティブasyncパターン

WASI 0.3では、WIT定義でasync関数を直接宣言でき、wit-bindgenが生成するバインディングがRustのasync/awaitに対応します。

// WASI 0.3のネイティブasyncを使ったHTTPクライアント例
// wit-bindgenが生成したバインディングを利用

// generated bindings
mod bindings {
    wit_bindgen::generate!({
        world: "my-component",
        // WASI 0.3のasyncインターフェースをインポート
        async: {
            imports: ["wasi:http/outgoing-handler#handle"],
        },
    });
}

use bindings::wasi::http::types::{
    Headers, OutgoingBody, OutgoingRequest, Scheme,
};

// asyncとして直接宣言可能(WASI 0.3)
async fn fetch_sensor_data(endpoint: &str) -> Result<Vec<u8>, String> {
    let headers = Headers::new();
    let request = OutgoingRequest::new(headers);
    request.set_scheme(Some(&Scheme::Https)).unwrap();
    request.set_authority(Some(endpoint)).unwrap();
    request.set_path_with_query(Some("/api/sensors")).unwrap();

    // WASI 0.3: 直接awaitできる(pollable不要)
    let response = bindings::wasi::http::outgoing_handler::handle(request, None)
        .await
        .map_err(|e| format!("HTTP error: {:?}", e))?;

    let status = response.status();
    if status != 200 {
        return Err(format!("Unexpected status: {}", status));
    }

    // stream<u8>からデータを読み込み(WASI 0.3のジェネリックストリーム)
    let body = response.consume().unwrap();
    let mut data = Vec::new();
    let stream = body.stream().unwrap();

    // stream<T>には自動バックプレッシャーが組み込まれている
    loop {
        match stream.read(4096) {
            Ok(chunk) if chunk.is_empty() => break,
            Ok(chunk) => data.extend_from_slice(&chunk),
            Err(_) => break,
        }
    }

    Ok(data)
}

WASI 0.2との比較ポイント:

最初はWASI 0.2と同じくpollableを取得してpoll-oneoff()を呼ぶアプローチを検討しましたが、WASI 0.3ではCanonical ABIレベルで非同期が実装されているため、awaitだけで済みます。開発者が手動でポーリングリストを管理する必要がなくなり、コード量が約40%削減されます。

複数I/Oの並行処理

組み込みシステムでは複数のセンサーデータを同時に取得するケースが多くあります。futures-concurrencyを使った並行処理パターンを見てみましょう。

use futures_concurrency::prelude::*;

async fn collect_sensor_data(reactor: &Reactor) -> SensorBundle {
    // 3つのセンサーからのデータ取得を並行実行
    let (temp, humidity, pressure) = (
        read_temperature_sensor(reactor),
        read_humidity_sensor(reactor),
        read_pressure_sensor(reactor),
    ).join().await;

    SensorBundle {
        temperature: temp.unwrap_or_default(),
        humidity: humidity.unwrap_or_default(),
        pressure: pressure.unwrap_or_default(),
        timestamp: get_current_time(),
    }
}

struct SensorBundle {
    temperature: f32,
    humidity: f32,
    pressure: f32,
    timestamp: u64,
}

// タイムアウト付きセンサー読み取り
async fn read_temperature_sensor(reactor: &Reactor) -> Result<f32, SensorError> {
    // WASI 0.2: subscribe_durationでタイマーpollableを取得
    let timeout = wasi::clocks::monotonic_clock::subscribe_duration(5_000_000_000);

    // センサーI/Oとタイムアウトを競合させる
    let sensor_pollable = get_sensor_pollable("temperature");

    // どちらかが先に完了するまで待機
    let ready_list = wasi::io::poll::poll(&[&sensor_pollable, &timeout]);

    if ready_list.contains(&1) {
        return Err(SensorError::Timeout);
    }

    // センサーデータを読み取り
    let raw_value = read_sensor_register(0x40)?;
    Ok(convert_to_celsius(raw_value))
}

#[derive(Debug)]
enum SensorError {
    Timeout,
    ReadError(String),
}

fn convert_to_celsius(raw: u16) -> f32 {
    // 典型的な温度センサーの変換式(例: TMP102)
    (raw as f32) * 0.0625
}

ハマりポイント: join()で並行実行する場合、すべてのFutureが同じReactorインスタンスを共有する必要があります。WASI 0.2のランタイムはシングルスレッドなので、Rc<RefCell<>>で参照を共有します。Arc<Mutex<>>を使うとコンパイルは通りますが、WASMのシングルスレッド環境では不要なオーバーヘッドになります。

パフォーマンス最適化のテクニック

組み込みシステムでWASMを実行する場合、パフォーマンス最適化は不可欠です。ここでは、コンパイル時・ランタイム・バイナリサイズの3軸で最適化手法を紹介します。

コンパイル時の最適化

# 1. リリースビルド(必須)
cargo build --target wasm32-wasip2 --release

# 2. wasm-optによるバイナリ最適化
# -Oz: サイズ最適化(組み込み向け推奨)
# -O3: 速度最適化(サーバーサイド向け)
wasm-opt -Oz -o optimized.wasm target/wasm32-wasip2/release/my_app.wasm

# 3. Wasmtime AOTコンパイル(デプロイ先がWasmtimeの場合)
wasmtime compile optimized.wasm -o precompiled.cwasm

# 4. Wizerによるプリイニシャライズ(起動時間短縮)
wizer optimized.wasm -o preinit.wasm

Cargo.tomlでプロファイル設定を最適化することも重要です。

# Cargo.toml
[profile.release]
opt-level = "z"          # サイズ最適化(組み込み向け)
lto = true               # Link-Time Optimization
codegen-units = 1        # 単一コード生成ユニット(最適化品質向上)
panic = "abort"          # パニック時のアンワインド無効化
strip = true             # デバッグシンボル削除

[profile.release.package."*"]
opt-level = "z"

ランタイム別パフォーマンス比較

組み込みシステム向けのWASMランタイムは用途に応じて選定する必要があります。2026年時点のベンチマーク結果を以下にまとめます。

ランタイム ネイティブ比性能 メモリ使用量 コンパイル方式 適用シーン
Wasmtime (AOT) 93% AOT サーバーサイド、エッジ
Wasmtime (JIT) 88% 中〜大 JIT 開発・テスト
WasmEdge 85-90% AOT/JIT IoTエッジ、AI推論
WAMR (AOT) 70-80% 279-480KB AOT/JIT リソース制約デバイス
wasm3 65% 134-156KB インタプリタ 超省メモリ環境

(出典: dasroot.net ベンチマーク記事arxiv.org IoTデバイス論文

なぜランタイム選定が重要か:

  • Wasmtime AOTはネイティブの93%の性能を達成しますが、ARM Cortex-M系の小型マイクロコントローラでは動作しません
  • wasm3は134KBのメモリフットプリントでRaspberry Pi Picoでも動作しますが、性能はネイティブの65%にとどまります
  • 用途に応じた選定が必要で、「全てのケースで最適なランタイム」は存在しません

マイクロコントローラでの実測ベンチマーク

arxiv.orgの2025年の研究論文では、3つのマイクロコントローラでWASM実行性能を測定しています。バブルソート(100要素)での結果は以下のとおりです。

デバイス ネイティブC wasm3 WAMR エネルギー比(WASM/Native)
Raspberry Pi Pico 612 µs - 29,475 µs 約52倍
ESP32 C6 578 µs 6,358 µs - 約11倍
nRF5340 864 µs - - 約33倍

この論文の著者らは、WASMの実行時間がネイティブの10〜50倍程度になると報告しています。エネルギー消費も同様の倍率で増加し、Raspberry Pi Picoではネイティブ0.06mJに対してwasm3が3.11mJを消費しました。

トレードオフの考え方: パフォーマンスオーバーヘッドは大きいですが、WASMには以下のメリットがあります。

  • ポータビリティ: 1つのバイナリを複数のマイクロコントローラで実行可能
  • セキュリティ: サンドボックス実行による分離
  • OTA更新: ファームウェア全体ではなく、WASMモジュール単位での更新が可能

注意: バッテリー駆動のIoTデバイスでは、WASMのエネルギーオーバーヘッドが電池寿命に直接影響します。センサーデータの前処理など、頻繁に実行される処理にはネイティブコードを使い、ビジネスロジック部分のみWASMで実装するハイブリッドアプローチが現実的です。

バイナリサイズ最適化の実践

組み込みシステムではフラッシュメモリの制約があるため、WASMバイナリのサイズ削減は重要です。

# 最適化前後のサイズ比較例
$ ls -lh target/wasm32-wasip2/debug/my_app.wasm
# 2.1M  (デバッグビルド)

$ ls -lh target/wasm32-wasip2/release/my_app.wasm
# 420K  (リリースビルド、-80%)

$ wasm-opt -Oz target/wasm32-wasip2/release/my_app.wasm -o optimized.wasm
$ ls -lh optimized.wasm
# 180K  (wasm-opt適用後、さらに-57%)

--releaseフラグとwasm-optの-Ozオプションの組み合わせで、バイナリサイズをデバッグビルドの約10分の1に削減できます。dasroot.netの記事によると、--releaseフラグだけでバイナリサイズを最大90%削減でき、wasm-optの適用でさらに大幅な縮小が可能です。

実践的なユースケース:IoTエッジデバイスでのセンサーデータ処理

ここまでの技術を統合した、実践的なユースケースを実装してみましょう。IoTエッジデバイスで複数のセンサーデータを非同期に収集し、ローカルで前処理を行うシステムです。

システムアーキテクチャ

実装コード

use wasi_async_runtime::{block_on, Reactor};
use std::collections::VecDeque;

/// センサーデータの集約と前処理を行うメインループ
fn main() {
    block_on(|reactor| async move {
        let mut aggregator = DataAggregator::new(10); // 10サンプルの移動平均

        loop {
            // 複数センサーからデータを並行取得
            let bundle = collect_sensor_data(&reactor).await;

            // 異常値フィルタリング
            if let Some(filtered) = aggregator.process(bundle) {
                // 閾値超過時のみクラウドに送信(帯域節約)
                if filtered.requires_alert() {
                    match send_to_cloud(&reactor, &filtered).await {
                        Ok(_) => log_event("alert_sent", &filtered),
                        Err(e) => log_event("send_failed", &e),
                    }
                }
            }

            // 次の読み取りまで待機(WASI 0.2: subscribe_durationを使用)
            let delay = wasi::clocks::monotonic_clock::subscribe_duration(
                1_000_000_000, // 1秒間隔
            );
            reactor.wait_for(delay).await;
        }
    });
}

/// 移動平均によるデータ平滑化とアラート判定
struct DataAggregator {
    window_size: usize,
    temp_history: VecDeque<f32>,
    humidity_history: VecDeque<f32>,
}

impl DataAggregator {
    fn new(window_size: usize) -> Self {
        Self {
            window_size,
            temp_history: VecDeque::with_capacity(window_size),
            humidity_history: VecDeque::with_capacity(window_size),
        }
    }

    fn process(&mut self, bundle: SensorBundle) -> Option<ProcessedData> {
        // 移動平均ウィンドウに追加
        self.temp_history.push_back(bundle.temperature);
        self.humidity_history.push_back(bundle.humidity);

        // ウィンドウサイズを超えたら古いデータを削除
        if self.temp_history.len() > self.window_size {
            self.temp_history.pop_front();
        }
        if self.humidity_history.len() > self.window_size {
            self.humidity_history.pop_front();
        }

        // ウィンドウが埋まるまでデータ収集を継続
        if self.temp_history.len() < self.window_size {
            return None;
        }

        let avg_temp = self.temp_history.iter().sum::<f32>()
            / self.temp_history.len() as f32;
        let avg_humidity = self.humidity_history.iter().sum::<f32>()
            / self.humidity_history.len() as f32;

        Some(ProcessedData {
            avg_temperature: avg_temp,
            avg_humidity,
            pressure: bundle.pressure,
            timestamp: bundle.timestamp,
        })
    }
}

struct ProcessedData {
    avg_temperature: f32,
    avg_humidity: f32,
    pressure: f32,
    timestamp: u64,
}

impl ProcessedData {
    /// 温度が40°C以上または湿度が90%以上でアラート
    fn requires_alert(&self) -> bool {
        self.avg_temperature > 40.0 || self.avg_humidity > 90.0
    }
}

fn log_event(event: &str, _detail: &dyn std::fmt::Debug) {
    // 構造化ログ出力(本番ではJSON形式)
    eprintln!("[{}] event={}", get_timestamp_str(), event);
}

ベルリンのスマート交通システムの事例では、WasmEdgeランタイムをARMマイクロコントローラ上で使用し、ローカルでのデータ前処理により帯域使用量を70%削減したと報告されています(dasroot.netによる)。

ハイブリッドアプローチの設計指針

組み込みシステムでWASMを効果的に使うには、すべてをWASMで実装するのではなく、ネイティブコードとの適切な分担が重要です。

処理内容 推奨実装 理由
センサードライバ ネイティブC/Rust ハードウェア直接アクセスが必要
データ前処理 WASM(条件付き) OTA更新可能、ただし頻度が高い場合はネイティブ
ビジネスロジック WASM ポータビリティとOTA更新の恩恵が大きい
通信プロトコル WASM セキュリティサンドボックスの恩恵
暗号処理 ネイティブ 性能要件が厳しい

よくある間違い: 最初はすべてのロジックをWASMで実装しようとしましたが、タイトなループで100倍以上のオーバーヘッドが発生するケースがありました。センサーの割り込みハンドラやリアルタイム制御など、マイクロ秒単位の応答が必要な処理はネイティブコードに任せるべきです。

よくある問題と解決方法

WASIを活用した非同期I/O開発で遭遇しやすい問題と、その解決方法を整理します。

問題 原因 解決方法
wasm32-wasip2でコンパイルエラー Rustツールチェーンが古い rustup update && rustup target add wasm32-wasip2
tokioがwasm32-wasiで動かない ソケット作成がWASIで非対応 wasi-async-runtimeまたはwasi-tokioを使用
WASMバイナリが大きすぎる デバッグビルド、未使用コード --release + wasm-opt -Oz + strip = true
起動時間が遅い 初期化処理のオーバーヘッド Wizerでプリイニシャライズ(32%改善)
メモリ不足(組み込み) WAMRのメモリ要件が高い wasm3に切り替え(134KB vs 279KB)
WASI 0.3のasyncが使えない Wasmtime 37未満 Wasmtimeを37以降にアップデート

デバッグのヒント

# WASMバイナリの内容を検査
wasm-tools print optimized.wasm | head -50

# Wasmtimeでの実行時ログ有効化
WASMTIME_LOG=wasmtime_wasi=trace wasmtime run optimized.wasm

# WASMバイナリのセクションサイズ分析
wasm-tools objdump optimized.wasm

まとめと次のステップ

まとめ:

  • WASI 0.3(2026年2月、Wasmtime 37+対応)は、Component Modelにネイティブ非同期サポートを導入し、stream<T>future<T>によってWASI 0.2の手動ポーリングを置き換えます
  • Wasmtime AOTコンパイルにより、ファイルI/O操作でネイティブC++の93%の性能を達成可能です。一方、マイクロコントローラ上では10〜50倍のオーバーヘッドがあり、用途に応じたランタイム選定が必要です
  • 組み込みシステムでは、ネイティブコードとWASMのハイブリッドアプローチが現実的です。ビジネスロジックとOTA更新が必要な部分にWASMを、ハードウェア制御とリアルタイム処理にネイティブコードを使い分けましょう
  • wasi-async-runtimeクレート(v0.1.2)がWASI 0.2環境での非同期プログラミングを支援しますが、WASI 0.3の安定化後はネイティブasyncへの移行を計画してください

次にやるべきこと:

  • WASI 0.3のロードマップを確認し、安定版リリースのタイミングを把握する
  • wasm32-wasip2ターゲットで小さなプロジェクトを作成し、非同期I/Oのパターンを実際に試す
  • 自分の組み込みターゲットデバイスでWAMRまたはwasm3のベンチマークを実行し、性能特性を把握する

参考


注意: この記事はAI(Claude Code)により自動生成されました。内容の正確性については複数の情報源で検証していますが、実際の利用時は公式ドキュメントもご確認ください。

3
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
3
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?