exif_imagetypeとgetimagesize、使うならどっち?

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
雄山がケフィアを食ったらどんな反応すんだ?

この記事を三行にまとめると

いいえ、ケフィアです
サラマンダーよりずっと早い!
ヨヨ王女は正しかった
そのファイルは画像でしょうか?

▶はい、画像です。
いいえ、ケフィアです。



もしこんなやり取りをPHPで行いたいときがあったとしよう。そんなときはどうすれば良いの? そもそもケフィアって何だっけ?

今日はそんな疑問を解決していこうと思います。



ケフィアって何だっけ?

ケフィアとは……。

乳製品の一つで、日本だとヨーグルトきのことかの名称でも知られていた気がする。ちなみに「いいえ、ケフィアです」というのは、株式会社やずやの千年ケフィアという商品のCMに出て来たフレーズで、一時期ものすごい流行った。きっと今でもyoutubeとかで動画を探せばすぐに見つかります。



画像かどうか判定するには?

PHPを使ってとあるファイルが画像ファイルであるかどうかを調べる方法はいくつかありますが、今回お話したいのはその中の二つのやり方。

「exif_imagetype」と「getimagesize」です。

exif_imagetypeは、単純にファイルが画像かどうかを判定するだけの関数です。返り値も、画像だったら画像の種類(jpgとかpngとか)に応じた定数が返って来るだけ。一方のgetimagesizeは、もう少しいろいろな情報が返って来ます。画像の大きさとかMIMEタイプとか。

画像の大きさまで情報として取得したいならgetimagesize()を使えば良いという話になりますが、画像かどうかだけ分かれば良いのであれば、どっちを使っても判定することができる。じゃあどっちを使うのが良いかっていう話なんですけど、PHPのマニュアルによれば、「getimagesize()よりもexif_imagetype()の方がずっと早く動作します」と書いてある。

なるほど。ずっと早いのならもう何も言うことはない。おとなしくexif_imagetype()を使えば良いじゃねーか。問題解決じゃねーか。ケフィアのくだりいらねーじゃねーか。無駄なこと書いてんじゃねーよ。マフィア呼んで命(たま)取んぞこら。

って思うでしょ? まあそりゃ思うよね。僕だって思ったさ。

でも本当にそうなのだろうか? 「ずっと早い」と書いてあるだけで簡単に採用して良いものだろうか。もっとよく考えた方が良いんじゃないだろうか。

思い出してほしい。その昔、スクウェアから発売されたバハムートラグーンというゲームで、「サラマンダーよりずっと早い!」という名言と共にビュウのもとを去ってパルパレオスに乗り換えたばっかりに、ヨヨ王女は未だにスクウェアゲームの三大悪女の一人と呼ばれていることを。つまり、ずっと早い、ただそれだけの理由で選んでしまったら、後世にまで語り継がれるほどの汚名を着せられる可能性が、あるかもしれないということだ。

でも人間は、過去のあやまちから学ぶことができる。先人たちが犯したあやまちから学び、同じ轍を踏まないことができる生き物なのだ。

だから我々も学ぼう。ヨヨ王女という偉大なヒロインの犠牲を無駄にしないために。



検証理由

僕が何でわざわざ待ったをかけているのかというと、exif_imagesize()の方は画像じゃないファイルを判定したときに、エラーが出ることがあるみたいなんですよ。

具体的に僕が出したエラーがどんなのかってーと、とあるファイルをexif_imagesize()で判定したときに、以下のようなエラーが出ました。

stream does not support seeking



Macで何枚か画像を入れたフォルダを圧縮してzipファイルを作成したときなんですが、何かファイルを解凍すると、フォルダの中に画像以外のファイルができてるんですね。

例えば、以下のようなファイルをフォルダに入れて圧縮し、それを後にサーバー上で解凍すると、フォルダの中身がこんな風になることがある。

//圧縮前
001.jpg
002.jpg

//解凍後
.DS_Store
._.001.jpg
._.002.jpg
001.jpg
002.jpg

二枚の画像をフォルダに入れて圧縮しただけなのに、解凍すると何かよく分からないファイルが増えてた。そんでもって、これらのファイルを画像か判定しようとすると、上記のエラーが出たと、そういうわけなのです。

画像以外の三つのファイルは隠しファイルってやつでして、詳しいことはよく分かりませんが、ファイルのキャッシュ情報みたいなのが入ってるとか何とかで……まあ、別にあって困るようなものではないです。ただこれがあることにより、フォルダの中から画像だけを抽出したいようなときに、それが画像かどうかを判定するという一手間を入れなければならないのもまた事実。

この手の隠しファイルを作らないようにする方法ってのもあるにはあるんですが、今日はそこは本題じゃないので省略です。それに自分はよくても、自分の作ったウェブサービスを利用してくれる一般のユーザーさんにそういうのを徹底させるってのも、難しいっすからね。

まあこの手のファイルに限らず、画像以外のファイルを読んだときにexif_imagetype()がエラーを出す可能性があることは、PHPのマニュアルにも書いてあることなので、そういう前提でシステムを組んでおくに越したことはない。



エラー演算子の存在

エラーが出ればそれは画像ではないという判定で良いかもしれないですが、だからって毎回エラーを垂れ流しておくのも気持ちが悪いですから、何とかしたい。

PHPには、エラーを無視するためのエラー演算子ってのがあります。「@」がそれに該当します。

@exif_imagetype('.DS_Store');

こんな風に処理を書くと、エラーが出てたとしても無視される。この場合だと先ほどのStream何ちゃらかんちゃらってエラーが出なくなる。

だったら@を使えば良いだけじゃねーか。話はこれで終わりじゃねーか。ケフィアのくだり無駄だったじゃねーか。ケフィアとマフィアかけて上手いこと言えたとか思ってんじゃねーよ。パフィア死ぬほど食わすぞこら。

って思うでしょ? まあそりゃ思うよね。僕だって思ったさ。

でもこれもそう簡単な話ではなくて……と言うのも、エラー演算子をつけると処理がものすごく遅くなるらしいんです。あっと驚くほど遅くなるんだそうです。アットマークだけに。

僕自身、あまり使ったことはないんですが、エンジニアの人のブログとか読んでると、相当に遅くなるらしいって話をよく目にする。プログラマは@を使うべきじゃないと言い切ってる人も見かける。まあ、単純に遅いってだけじゃなくて、エラーを出さないようにするとバグとか見逃しちゃう可能性もあるからとか、そういう別の理由もあるんですが、とにかく、エラー演算子ってのは随分な悪者扱いのようです。まるでヨヨ王女のようじゃないか、HAHAHA。



ちなみにパフィアというのはブラジル辺りに生えてる植物です。万病に効くと言われています。

万病に効く薬を死ぬほど摂取したらどうなるんだろうね? 回復しながら死んでいくんだろうか。まるでアンデット系のモンスターに回復呪文をかけるようじゃないか、HAHAHA。



速度検証1

実際にどれくらい遅いのか。せっかくなんで、ちょっとやってみましょう。その上で、getimagesize()を使うのか、@つきでexif_imagetype()を使うのか改めて決めることにしましょう。

$start = microtime(true);

$images = '画像が300枚ほど';
foreach($images as $i) {
  getimagesize($i);
}

$end = microtime(true);
echo $end - $start;

ざっくりこんな感じで良いでしょう。

$imagesには画像のURLが配列で入ってると思って下さい。300枚にした理由は特にありません。ファイルサーバーの中から適当に画像がいっぱい入ってるフォルダ探したら、300枚くらい入ってるフォルダがあったんで、それ使っただけです。正確には316枚です。

で、実際にやってみました。今回は5回ほど計測して、その平均値を取ってみました。

その結果タイムがこちら。

17.58秒

うん、まあ、これ自体が早いか遅いかは分かりません。サーバーのネットワークの問題とかもあるでしょうしね。



では次はexif_imagetype()です。エラー演算子はつけないで。

$start = microtime(true);

$images = '画像が300枚ほど';
foreach($images as $i) {
  exif_imagetype($i);
}

$end = microtime(true);
echo $end - $start;

5回の計測の平均はこちら。

17.20秒

うーん、まあ……確かにexif_imagetype()の方が早いは早いけど……ずっと早いってほどでもないよなぁ……。

検証の仕方を間違えたのかな。本当はもっとずっと早いのかも。でもとりあえず今回はこういう結果になりました。



じゃあ次はエラー演算子つきでやってみましょう。ま、ある意味ここが本命ですからね。ここですごい遅くなれば、今回の検証は成功ってことだ。

$start = microtime(true);

$images = '画像が300枚ほど';
foreach($images as $i) {
  @exif_imagetype($i);
}

$end = microtime(true);
echo $end - $start;

5回の平均タイムはこれだぁ!

17.41秒

ちょwwおまwwwwほとんど変わらんやんけ! そしてわずかだけどgetimagesize()よりも早いやんけ。



ということで、順位はこうなりました。

1位・・・exif_imagetype(17.20秒)
2位・・・@exif_imagetype(17.41秒)
3位・・・getimagetype(17.58秒)

あれれー? おかしいぞー。やっぱり何か検証のしかたを間違えたか? それともエラー演算子をつけると遅いっていうのは、むかーしむかしのことじゃったのか? テレビのCMで「いいえ、ケフィアです」っていうセリフが毎日のようにお目にかかれた時代のことなのか?

ううむ……マジでどうしよう。別に事実を捏造してはいないので、マジでほんとにこういう結果にはなったのですが……エラー演算子を使うと遅いという事実を誰かに保証してもらおうとしてこの記事に辿り着いた人がいたら、混乱させてしまうぞ……これ。



速度検証2

もしや、エラーが発生しない条件で検証したからいけなかったのかな。エラーが発生すれば、@を使う方が圧倒的に遅くなるのかもしれない。あっとうてきに遅くなるのかもしれない。アットマークだけに。

ということで、今度は画像以外のものも入れて、具体的に言うとさっき出て来た隠しファイルを意図的に入れて、わざとエラーが出るようにして実験してみることにしました。

さっきは画像300枚でやったんだけど、今度は画像と隠しファイルを半々くらいの割合で、合計30ファイルほど入ったフォルダで同じことを遣ってみました。急にファイル数が1/10になっちゃったけど、勘弁。隠しファイルと画像が良い感じに入ってるフォルダがそれしかなかったのよ。本当はもっとちゃんと用意すれば良かったんだけど……。

検証の仕方はさっきと一緒です。30枚ほどあるファイルをgetimagesize()とexif_imagetype()でそれぞれ舐めてみる。タイムは5回の平均を算出。

まずはgetimagesize()から。

$start = microtime(true);

$files = 'ファイルが30個ほど';
foreach($files as $f) {
  getimagesize($f);
}

$end = microtime(true);
echo $end - $start;

結果は以下の通り。

3.99秒

ふむ、なるほど。まあ、ファイル数が減ったんだから、そりゃ時間も短くなりますわな。



では次。エラー演算子を使わないexif_imagetype()の場合。

$start = microtime(true);

$files = 'ファイルが30個ほど';
foreach($files as $f) {
  exif_imagetype($f);
}

$end = microtime(true);
echo $end - $start;

タイムを見てみましょう。

3.26秒

さっきと似たような結果ですね。そこまで大きな差ではないようにも見えるけど、getimagesize()よりはexif_imagetype()の方が早い。まあ、コンピュータの世界だったら0.7秒の差は結構でかいか。ブラウザの表示時間も、1秒早くなればPVが激増するという噂もあるし。



ほんじゃ、次。エラー演算子つき。

$start = microtime(true);

$files = 'ファイルが30個ほど';
foreach($files as $f) {
  @exif_imagetype($f);
}

$end = microtime(true);
echo $end - $start;

さあ、気になるタイムの方はどうでしょうか。

2.89秒

おいぃぃぃ!! まさかの最速ww 一人だけ3秒切りおったで。



1位・・・@exif_imagetype(2.89秒)
2位・・・exif_imagetype(3.26秒)
3位・・・getimagesize(3.99秒)

エラー演算子つきが一位になるというまさかの事態になってしまいました。つけると圧倒的に遅くなると世間的には言われているのに、まさか圧倒的に早くなるという結果をお知らせすることになろうとは……やっぱり何か検証の仕方を間違えたのかなぁ? すごい不安になって来たぞ。

あれかな。隠しファイルをexif_imagetype()で判定したとき、@なしの方はエラーが画面に出力されてるんで、その出力分だけ処理時間が遅くなってるとか、そういうのあるのかな。今回は15個ほど画像じゃないファイルを混ぜたんで、例のstream何ちゃらっていうエラーメッセージが画面にバーッと15個ほど出力されてます。@ありの方はエラーを無視してるから画面は真っ白なまま。時間だけが表示される。

そう思って、一応エラーを画面に出力せずに@をつけないでやってみたんですけど、結果はほとんど変わりませんでした。約3.2秒でした。

ちょっとファイル数が少なかったかもしれないね。300個とか1000個とかでやれば、もっと違う結果になったかもしれない。ただまあ、僕が実際にこの処理を使って運用しているところは、そんな一度に300個とか1000個のファイルを一覧で取得してその中から画像だけを取り出してブラウザ上に表示、なんてことはやらないので、実際的なケースを想定するとだいたい30個くらいなもんなので、今回はこの結果で納得しておくことにします。逆に言えば、1000個くらいなら顕著な差が出るかもしれないけど、30個くらいだとこんな感じになるってんなら、喜んでエラー演算子を使うっていう結論で良いかなと。





というわけで、わざわざ検証した挙げ句、やっぱりexif_imagetype()を使う方で良いんじゃないかという結論になりました。ずっと早い方を採用で普通に正しかったわ。どういうわけかエラー演算子を使うと更に早くなるという結果になったのが、いささか不安ではありますが……まあ良いでしょう。

しかし……だいぶ予定が狂っちゃったなー。

僕の当初の予定では、exif_imagetype()の方が早いことは疑ってないけど、エラー演算子を使うことによってgetimagesize()よりも遅くなる可能性があるから、エラー演算子を使わざるを得ないような状況であれば、あえて動作が遅いgetimagesize()を採用する必要もあるだろう的な話になるはずだったんですよ。本当なら今頃このオチの部分で、それを言ってるはずだったんですよ。ヨヨ王女のようになってはいけない(ドヤァッ)って感じで、締めくくるはずだったんですよ。

ところがふたを開けてみたらあんた……これ完全にヨヨ王女が正義だよ……完全にずっと早い方を選んで正解だよ。
 もしかしたら何か関連しているかも? 
 みんなからのコメント 
2015年01月16日 10:10:51
とおりすがり
はじめまして。記事拝読いたしました。
exif_imagetypeが気になる動作をするので、
原因をググっていたらたどり着きました。

これ、なにがしかキャッシュしちゃってませんかねえ。
@つきの検証が毎回一番最後になっているようですので、
一番最初にやってみたらヨヨ女王の鼻を明かせるかも...
と、検証もせずにコメントしてみました。

ちなみに気になる動作というのは、使用メモリの量がハンパないということです。
スマホから撮ったままの画像をアップロードして解析するときに使用しているのですが、ファイルサイズとか関係あるんですかねー。
2015年01月16日 21:11:56
まっち~(管理人)
>とおりすがりさん
はじめまして。コメントありがとうございます!

一応キャッシュは消しながらやってたはずなんですけど、でもいざそう言われると、もしかしたら消さずにやってしまっていたかも……。

すみません。またちょっと改めてやってみます。今度は確実にキャッシュを消して。
ヨヨ王女の鼻を明かすチャンスを逃す手はないですからね。
順番もそうですね、変えてやってみたら結果も変わるかもしれません。

メモリの使用量が大きいのは、うーん……何でだろう。
コメントいただいた後、30KB程度の画像と3MB程度の画像をそれぞれ10枚用意して試してみたんですが、僕がやった限りではメモリの使用量は同じでした。

ファイルサイズ以外に何か理由があるのかもしれません。何か解決のヒントになりそうな情報を手に入れることができれば、追記させてもらいますね。今はお役に立てずすみません。