Python」タグアーカイブ

ClutterImage PyGObject/Gjs

おまたせ、Comipoli Gjs 版が遅くて出せない原因が判明しました。

#!/usr/bin/env python3

import gi
gi.require_version('Clutter', '1.0')
gi.require_version('GdkPixbuf', '2.0')
from gi.repository import Clutter, Cogl, GdkPixbuf

Clutter.init()

PICTURE = "burgman400.jpg"

pixbuf = GdkPixbuf.Pixbuf.new_from_file(PICTURE)
image = Clutter.Image()
image.set_data(
    pixbuf.get_pixels(),
    Cogl.PixelFormat.RGB_888,
    pixbuf.get_width(),
    pixbuf.get_height(),
    pixbuf.get_rowstride()
)

#!/usr/bin/gjs

const Clutter = imports.gi.Clutter;
const Cogl = imports.gi.Cogl;
const GdkPixbuf = imports.gi.GdkPixbuf;

Clutter.init(null);

const PICTURE = "burgman400.jpg";

let pixbuf = GdkPixbuf.Pixbuf.new_from_file(PICTURE);
let image = new Clutter.Image();
image.set_data(
    pixbuf.get_pixels(),
    Cogl.PixelFormat.RGB_888,
    pixbuf.get_width(),
    pixbuf.get_height(),
    pixbuf.get_rowstride()
);

何ですかこの圧倒的なスピード差は!!!
というより Gjs のこの異様な遅さは何なんだ?
GdkPixbuf を使うだけなら特に差が無いのに。

憶測だけど PyGObject は get_pixels でバイナリ出力を直接使っていると思う。
Gjs は多分バイナリ出力を Uint8Array オブジェクトに変換している。
言語仕様の制限だろうからセット関数の追加が無いかぎりこのままだろうね。

ClutterImage に画像セットはコレしか手段が無い、これは困った。
Gjs でいくなら ClutterCnavas で cairo という手しか無いっぽい。
でもそれなら GtkDrawinArea でいいじゃん、ということになり…

結論、Clutter で画像を使うのに Gjs は絶望的に向いていません。

Gjs VS PyGObject (Linux time command)

Linuxコマンド集 – 【 time 】 指定したコマンドの実行時間を表示する:ITpro

なんだよ、こんな便利なコマンドがあったのかい!
これで Python と Gjs の速度比較が簡単になる。

function printDateFormat(ms) {
    let date = new Date(ms);
    let s = date.getMinutes() + ":" + date.getSeconds() + ":" + date.getMilliseconds();
    print(s);
}

ってのを Gjs 用に考えたけどいらなかった。

てなわけで、早速 Gio.Subprocess で差が出るか確認してみよう。
前回のコードから不要な部分をバッサリ省いて。

#!/usr/bin/env python3

import gi
gi.require_version('GdkPixbuf', '2.0')
from gi.repository import Gio, GLib, GdkPixbuf

namelist = []
unzip_list = []

sp = Gio.Subprocess.new(["unzip", "-Z", "test.cbz"], Gio.SubprocessFlags.STDOUT_PIPE);
istream = sp.get_stdout_pipe()
dstream = Gio.DataInputStream(base_stream=istream)
while True:
    line, l = dstream.read_line_utf8()
    if line == None: break
    if line.startswith('-'):
        name = line[53:]
        if GLib.Regex.match_simple("\.(jpe?g|png|gif)$", name, GLib.RegexCompileFlags.CASELESS, 0):
            namelist.append(name)

for name in namelist:
    sp = Gio.Subprocess.new(["unzip", "-p", "test.cbz", name], Gio.SubprocessFlags.STDOUT_PIPE)
    stream = sp.get_stdout_pipe()
    p = GdkPixbuf.Pixbuf.new_from_stream(stream, None);
    unzip_list.append(p)
    stream.close()

Gjs で書き換えて。

#!/usr/bin/gjs

const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const GdkPixbuf = imports.gi.GdkPixbuf;

let namelist = [];
let unzip_list = [];

let sp = Gio.Subprocess.new(["unzip", "-Z", "test.cbz"], Gio.SubprocessFlags.STDOUT_PIPE);
let istream = sp.get_stdout_pipe();
let dstream = new Gio.DataInputStream({base_stream:istream});
for (;;) {
    let [line, l] = dstream.read_line_utf8(null);
    if (line == null) break;
    if (line.startsWith('-')) {
        let name = line.slice(53);
        if (GLib.Regex.match_simple("\.(jpe?g|png|gif)$", name, GLib.RegexCompileFlags.CASELESS, 0))
            namelist.push(name);
    }
}

for (let i=0; i<namelist.length; i++) {
    let sp = Gio.Subprocess.new(["unzip", "-p", "test.cbz", namelist[i]], Gio.SubprocessFlags.STDOUT_PIPE);
    let stream = sp.get_stdout_pipe();
    let p = GdkPixbuf.Pixbuf.new_from_stream(stream, null);
    unzip_list.push(p);
    stream.close(null);
}

コレを time コマンドで比較。

って一秒すらも差が出ない誤差の範囲ジャン!
Gjs 版 Comipoli が異様に遅い原因は言語のせいでは無いことは判明した。

zipfile module VS unzip command

Comipoli を PyGObject から Gjs に変更すると以前書いた。
実際に作っているんだけど困った、遅すぎる…

もしかして Gjs って遅いの?
いや単に zipfile モジュールが早いだけかも、実験だ。
zipfile を使うから当然 Python で。
blog で Python を書くのは久々のような。

#!/usr/bin/env python3

import time, zipfile, gi
gi.require_version('GdkPixbuf', '2.0')
from gi.repository import Gio, GLib, GdkPixbuf

namelist = []
zipfile_list = []
unzip_list = []

sp = Gio.Subprocess.new(["unzip", "-Z", "test.cbz"], Gio.SubprocessFlags.STDOUT_PIPE);
istream = sp.get_stdout_pipe()
dstream = Gio.DataInputStream(base_stream=istream)
while True:
    line, l = dstream.read_line_utf8()
    if line == None: break
    if line.startswith('-'):
        name = line[53:]
        if GLib.Regex.match_simple("\.(jpe?g|png|gif)$", name, GLib.RegexCompileFlags.CASELESS, 0):
            namelist.append(name)

# zipfile speed
now = time.time()
for name in namelist:
    with zipfile.ZipFile("test.cbz") as o:
        data = o.read(name)
        stream = Gio.MemoryInputStream.new_from_data(data)
        p = GdkPixbuf.Pixbuf.new_from_stream(stream)
        zipfile_list.append(p)
        stream.close()
print(time.time() - now)

# unzip command speed
now2 = time.time()
for name in namelist:
    sp = Gio.Subprocess.new(["unzip", "-p", "test.cbz", name], Gio.SubprocessFlags.STDOUT_PIPE)
    stream = sp.get_stdout_pipe()
    p = GdkPixbuf.Pixbuf.new_from_stream(stream, None);
    unzip_list.append(p)
    stream.close()
print(time.time() - now2)

少し大きめの cbz を用意して。

やはり zipfile モジュールのほうが少し展開が早いんだね。
いや、Gjs で作りかえた Comipoli の遅さはこんなレベルじゃないんだが。
ぶっちゃけ二倍くらい表示に時間が掛かる、とても出せるシロモノではない。

やっぱり Gjs が遅いのかも。
Gjs で同様なサンプルをと思ったけど time.time() の代替は何だ?
g_timer_new とかってバインドされていないのね、GDateTime あたりかな。
それとも他に原因があるかもしれないし、今日はここまで。

ただ Gjs への書き換えをやったおかげで beta12 で多重展開していたのを見つけた。
修正したらスゲェ速くなったので PyGObject のまま beta13 公開。
たまにはこうやって丸ごと書き換えるといいこともあるもんだ。

Python __getitem__

自作 comicbook archive ビューアがまだまだ気に入らない。
ページが多い場合にとにかく遅い、理由は解っているけど。
全ページをメモリに展開し更にリサイズして配列に格納していたらそりゃ遅い。

マルチスレッド化とか色々試したけど Python じゃ無理だ。
サムネイル表示時にもう一度リサイズするとかなんか無駄なことしているし。

ページめくり高速化の為に全部読み込んでいたけど都度読み込みのほうが良さげ。
ということでクラスを作ったけど今まで配列から取り出ししていたんだよなぁ。
全部の処理を関数に書き換えするのが面倒臭いし間違えるのが怖い。

いや、コイツは Python で作っている。
__getitem__ を使えばいいじゃないか。

#!/usr/bin/env python3

from gi.repository import GLib, Gio, GdkPixbuf
import os, zipfile

class ComipoliArchive:
    def __init__(self):
        self.status = 0
        self.namelist = []

    def new_cba(self, uri, is_unrar, is_7za):
        self.arc = Gio.file_new_for_uri(uri)
        self.path = self.arc.get_path()
        ext = os.path.splitext(self.path)[1].lower()
        if ext == ".cbz":
            with zipfile.ZipFile(self.path) as o:
                l = o.namelist()
                l.sort()
                for name in l:
                    if GLib.Regex.match_simple("\.(jpe?g|png|gif)$", name, GLib.RegexCompileFlags.CASELESS, 0):
                        self.namelist.append(name)
        elif is_unrar and ext == ".cbr":
            self.status = 1
            result, output, error, status = GLib.spawn_command_line_sync('unrar vt -p- -- "{0}"'.format(self.path))
            lines = output.decode("utf-8").split("\n")
            for line in lines:
                s = line.lstrip()
                if s.startswith('Name: '):
                    name = s[6:]
                    if GLib.Regex.match_simple("\.(jpe?g|png|gif)$", name, GLib.RegexCompileFlags.CASELESS, 0):
                        self.namelist.append(name)
        elif is_7za and ext == ".cb7":
            self.status = 2
            result, output, error, status = GLib.spawn_command_line_sync('7za l -slt "{0}"'.format(self.path))
            lines = output.decode("utf-8").split("\n")
            for line in lines:
                if line.startswith('Path'):
                    name = line[7:]
                    if GLib.Regex.match_simple("\.(jpe?g|png|gif)$", name, GLib.RegexCompileFlags.CASELESS, 0):
                        self.namelist.append(name)
        else:
            return 1
        return 0

    def __getitem__(self, num):
        stream = None
        if (self.status == 0):
            with zipfile.ZipFile(self.path) as o:
                data = o.read(self.namelist[num])
                stream = Gio.MemoryInputStream.new_from_data(data)
        elif (self.status == 1):
            sp = Gio.Subprocess.new(["unrar", "p", "-inul", "-@", "--", self.path, self.namelist[num]], Gio.SubprocessFlags.STDOUT_PIPE)
            stream = sp.get_stdout_pipe()
        elif (self.status == 2):
            sp = Gio.Subprocess.new(["7za", "x", "-so", self.path, self.namelist[num]], Gio.SubprocessFlags.STDOUT_PIPE)
            stream = sp.get_stdout_pipe()
        p = GdkPixbuf.Pixbuf.new_from_stream(stream)
        stream.close()
        return p

    def __len__(self):
        return len(self.namelist)

イケるやん!

これで今まで配列から GdkPixbuf を取り出ししていたように都度展開だ!
この機能はどんな時に使うんだ?と思っていたけどこういう場合なのね、ふむふむ。
yield なんていらなかったんや!まあ勉強になったしイイけど。

どうでもいいけど beta11 は arcive と誤字していた、次で直す。

GIOChannel @ Gjs, PyGObject

全体的に内容が古くなった Tips ページの更新を昨年末から地味に。
次は我がサイトの最大コンテンツである PyGObject のページ。

blog ではやったけどまとめていない GIOChannel, GSubprocess, GRegex 等々。
ただ Gjs でやってるのよね、メイン言語が今は JavaScript ですし。
書き換えするより Gjs の新規ページを作ったほうがいいかなと。

まあどちらでも基本的には変わらないし。
と思っていたけど…

GNOME 3.22 時の記事です、GIOChannel でファイルの読み書きを。

Gjs

#!/usr/bin/gjs
 
const GLib = imports.gi.GLib;
 
let s = "abcdefg\nあいうえお\n3行目";
 
let channel = GLib.IOChannel.new_file("output_js.txt", "w");
channel.write_chars(s, -1);
channel.shutdown(true);
 
let channel2 = GLib.IOChannel.new_file("output_js.txt", "r");
let [status, str_return] = channel2.read_to_end();
print(str_return);
channel2.shutdown(true);

PyGObject

#!/usr/bin/env python3

from gi.repository import GLib

s = "abcdefg\nあいうえお\n3行目"

channel = GLib.IOChannel.new_file("output_py.txt", "w")
#channel.write_chars(s, -1) # TypeError: Item 0: Must be number, not str
channel.write(s) # deprecated
channel.shutdown(True)
 
channel2 = GLib.IOChannel.new_file("output_py.txt", "r")
status, str_return = channel2.read_to_end()
print(str_return.decode("utf-8"))
channel2.shutdown(True)

で同様になる。

PyGObject は g_io_channel_write_chars が使えない。
g_io_channel_write は既に非推奨、これは困る。

それより read での挙動が違うんですけど。
Gjs は JavaScript 文字列で戻るけど PyGObject はデコードが必要。
write はそのまま UTF-8 で書き出しなのにチグハグです。

同じライブラリを使っているはずなのに。
やはり Gjs でまとめ直したほうが無難っぽい。