Help us understand the problem. What is going on with this article?

WebView+Canvas vs ネイティブ パフォーマンス比較 iOS編

More than 3 years have passed since last update.

WebView+Canvas+JavaScriptとネイティブのパフォーマンスを比較するために、実験を行ってみました。
非常に大雑把な比較になりますので、その点はご容赦ください。
Simulator Screen Shot 2016.02.29 11.37.23.png

検証方法
→色のついた正方形を複数画面内でランダムな方向に移動させる。WebViewとネイティブでそれぞれ正方形の数を変化させて、パフォーマンスの推移を測定する。

検証環境
→Xcode7.2.1 iPhone6 Plus iOS9.2.1

WebView+Canvas+JavaScript側の実装
→CanvasとJavaScriptのロジックを含むindex.htmlをバンドルし、UIWebViewで表示させる。20ミリ秒間隔で、fillRect関数を用いて複数の正方形を描画する。WKWebViewは原因不明の理由によりロジックがうまく動作せず、またパフォーマンスが表示されないため用いない。

ネイティブ側の実装
→UIViewで複数の青い正方形を作りself.viewにaddSubViewする。20ミリ秒間隔で、正方形の座標を変更する。

結果
スクリーンショット 2016-02-29 11.05.36.png
Nは正方形の数です。
スクリーンショット 2016-02-29 11.05.51.png
スクリーンショット 2016-02-29 11.06.01.png
予想通りネイティブの方がパフォーマンスは上ですが、WebViewの方がどうしようもないほどパフォーマンスが劣っているわけではなさそうです。
なお、ネイティブの方はN数が大きくなるとCPUの使用量が頭打ちになりますが、その分fpsを落として対応しているようです。WebViewの方は、N数が増えても確認できる範囲ではCPUの消費量は頭打ちになりませんでした。そのため、N数が多いとiPhoneが発熱するので少々危険かもしれません。

画像を用いたり、色数を増やしたりすると結果が異なってくるとは思うのですが、大雑把にWebView+Canvas+JavaScriptとネイティブのパフォーマンス差が把握できたかと思います。
Webの普遍性を優先するのか、ネイティブのパフォーマンスを優先するのか判断する際の一助になればと思います。

Androidでも同様の実験を行ってみたいですが、機種差が激しそうですね。

以下に、今回用いたコードを記述します。
→WebView + Canvas + JavaScript

index.html
<!DOCTYPE html>

<html>
<head>
  <title>-Experiment-</title>
</head>

<body style="padding:0px;margin:0px;">
  <canvas id="main_canvas"></canvas>
  <script type="text/javascript">

  var canvas = document.getElementById("main_canvas");
  canvas.style.width = window.innerWidth + 'px';
  canvas.style.height = window.innerHeight + 'px';
  canvas.style.margin = "0px";
  canvas.style.transform = "translate3d(0, 0, 0)";
  canvas.width = window.innerWidth * window.devicePixelRatio;
  canvas.height = window.innerHeight * window.devicePixelRatio;
  var context = canvas.getContext("2d");

  var balls = [];
  var speed = canvas.width * 0.002;
  var size = canvas.width * 0.05;
  for (var i=0; i<1000; i++) {
    var angle = Math.PI*2*Math.random();
    var ball = {
      size:size,
      position:{x:canvas.width/2, y:canvas.height/2},
      speed:{x:speed*Math.cos(angle), y:speed*Math.sin(angle)},
      border:{left:size/2, right:canvas.width-size/2, top:size/2, bottom:canvas.height-size/2},
    };
    balls.push(ball);
  }

  context.fillStyle = "#0000ff";

  var timer = setInterval(function(){

    context.clearRect(0, 0, canvas.width, canvas.height);

    for (var i=0; i<balls.length; i++){
      var ball = balls[i];
      ball.position.x += ball.speed.x;
      if (ball.position.x < ball.border.left) {
        ball.position.x = ball.border.left;
        ball.speed.x = Math.abs(ball.speed.x);
      };
      if (ball.position.x > ball.border.right) {
        ball.position.x = ball.border.right;
        ball.speed.x = -Math.abs(ball.speed.x);
      };
      ball.position.y += ball.speed.y;
      if (ball.position.y < ball.border.top) {
        ball.position.y = ball.border.top;
        ball.speed.y = Math.abs(ball.speed.y);
      };
      if (ball.position.y > ball.border.bottom) {
        ball.position.y = ball.border.bottom;
        ball.speed.y = -Math.abs(ball.speed.y);
      };

      context.fillRect(
        Math.round(ball.position.x-ball.size/2),
        Math.round(ball.position.y-ball.size/2),
        Math.round(ball.size),
        Math.round(ball.size)
      );
    }
  } , 20);

</script>
</body>
</html>
ViewController.swift
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        let url = NSBundle.mainBundle().pathForResource("index", ofType: "html");
        let reqURL = NSURL(string: url!)
        let req = NSURLRequest(URL: reqURL!)
        let webView = UIWebView(frame: CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height))
        webView.backgroundColor = UIColor.yellowColor()
        self.view.addSubview(webView)
        webView.loadRequest(req)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

→ネイティブ

ViewController.swift
import UIKit

class ViewController: UIViewController {

    var balls:[Ball] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        let speed = self.view.frame.size.width * 0.002
        let size = self.view.frame.size.width * 0.05
        for var i=0; i<1000; ++i{
            let ball = Ball()
            ball.frame = CGRectMake(0, 0, size, size)
            ball.center = self.view.center
            let rand = CGFloat(arc4random_uniform(UINT32_MAX)) / CGFloat(UINT32_MAX)
            let angle = CGFloat(M_PI*2)*rand
            ball.speed = CGVectorMake(speed*cos(angle), speed*sin(angle))
            ball.border = Border(
                left:size/2,
                right:self.view.frame.size.width-size/2,
                top:size/2,
                bottom:self.view.frame.size.height-size/2
            )
            ball.backgroundColor = UIColor.blueColor()
            self.view.addSubview(ball)
            balls.append(ball)
        }

        NSTimer.scheduledTimerWithTimeInterval(
            0.02,
            target: self,
            selector: "doFrame",
            userInfo: nil,
            repeats: true
        )
    }

    func doFrame(){
        for var i=0; i<balls.count; ++i {
            let ball = balls[i]
            ball.center.x += ball.speed!.dx
            if ball.center.x < ball.border?.left{
                ball.center.x = ball.border!.left!
                ball.speed?.dx = abs(ball.speed!.dx)
            }
            if ball.center.x > ball.border?.right{
                ball.center.x = ball.border!.right!
                ball.speed?.dx = -abs(ball.speed!.dx)
            }
            ball.center.y += ball.speed!.dy
            if ball.center.y < ball.border?.top{
                ball.center.y = ball.border!.top!
                ball.speed?.dy = abs(ball.speed!.dy)
            }
            if ball.center.y > ball.border?.bottom{
                ball.center.y = ball.border!.bottom!
                ball.speed?.dy = -abs(ball.speed!.dy)
            }
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}
Ball.swift
import UIKit

class Ball: UIView {
    var speed:CGVector?
    var border:Border?
}

struct Border {
    var left:CGFloat?
    var right:CGFloat?
    var top:CGFloat?
    var bottom:CGFloat?
}
yuky_az
「ヒトとAIの共生」がミッションの会社、SAI-Lab株式会社の代表取締役。 東北大学大学院理学研究科修了。理学博士(物理学)。 著書に「はじめてのディープラーニング」、「No.1スクール講師陣による 世界一受けたいiPhoneアプリ開発の授業」。オンライン教育プラットフォームUdemyで、2万人近くを指導する人気講師。
https://sai-lab.co.jp
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