Hatena::ブログ(Diary)

Memo

2016-04-30

[] Vim の起動速度2

前回 Vim の packadd を使って、起動速度改善をした。

その後 MacVim Kaoriya に Timer 機能が追加されたバージョンが出たので、Timer に置き換えた。

.vimrc は以下みたいな感じで定義。

let s:plugins = [
  \ 'vim-l9',
  \ 'vim-fuzzyfinder',
  \ 'vim-fuzzyfinder-plugins',
  \ 'yankround.vim',
  \ ]
let s:idx = 0

function! PackAddHandler(timer)
  execute 'packadd ' . s:plugins[s:idx]
  let s:idx += 1
  "doautocmd BufReadPost
  autocmd! lazy_load_bundle
endfunction

augroup lazy_load_python
  autocmd! VimEnter *.py call timer_start(1, 'LazyLoadPython', {'repeat': 0})
augroup END

augroup lazy_load_filetype
  autocmd! VimEnter *.go,*.js,*.php call timer_start(1, 'LazyLoadFileType', {'repeat': 0})
augroup END

augroup lazy_load_bundle
  autocmd VimEnter * call timer_start(1, 'PackAddHandler', {'repeat': len(s:plugins)})
augroup END

.vim/config/plugins.vim

function! LazyLoadPython(timer) abort
  let _ft = &filetype
  packadd jedi-vim
  packadd pyflakes-vim
  packadd vim-autopep8
  packadd vim-virtualenv

  " Reload autocmd
  exec 'doautoall BufRead'

  " For Quickrun.vim
  exec 'set ft=' . _ft

  autocmd! lazy_load_python
endfunction 

function! LazyLoadFileType(timer) abort
  let _ft = &filetype
  echomsg string(_ft)
  if _ft =~ 'go'
    packadd vim-ft-go
    packadd vim-go-extra
    packadd vim-godef
  elseif _ft =~ 'javascript'
    packadd! vim-javascript
    packadd! vim-jsx
    packadd vim-jsdoc
  elseif _ft =~ 'php'
    packadd vmustache
    packadd pdv
  endif
  exec 'doautoall BufRead'
  " For Quickrun.vim
  exec 'set ft=' . _ft
  autocmd! lazy_load_filetype
endfunction

~/.vim/pack/ftbundle にファイルタイプごとのプラグインを入れている。

ftbundle は Timer を使って、Vim 起動後にバックグラウンドプラグインを読み込む。


他のプラグインは ~/.vim/pack/bundle/start というところに入れて、起動時に読み込むようにしている。


これで起動速度がどうなったかというと、vim-plug と比べてみた。

vim-plug の速度。

357.614  030.946: opening buffers
359.083  001.469: BufEnter autocommands
359.089  000.006: editing files in windows
359.430  000.341: VimEnter autocommands
359.436  000.006: before starting main loop
360.991  001.555: first screen update
360.993  000.002: --- VIM STARTED ---

packadd の速度。

132.434  030.058: opening buffers
132.907  000.473: BufEnter autocommands
132.913  000.006: editing files in windows
134.438  000.653  000.653: sourcing /Users/s_ohyanagi/.vim/pack/bundle/start/unite.vim/autoload/unite/custom.vim
139.671  004.501  004.501: sourcing /Users/heavenshell/.vim/pack/bundle/start/vim-watchdogs/autoload/watchdogs.vim
143.496  010.225  005.071: sourcing /Users/heavenshell/.vim/config/plugins.vim
143.862  000.724: VimEnter autocommands
143.868  000.006: before starting main loop
145.161  001.293: first screen update

起動速度が大体倍ほど速くなった。

2016-03-21

[] Jedi.vimVim の起動速度

あるいは、いかにして Vim の起動速度を取り戻したか。


いつのころか Vim の起動速度が遅くなった。

正確に言うと、Python のファイルを起動する際に遅い。


とりあえず起動時に何が遅いかプロファイルを取ってみた。

954.157  614.589  614.589: sourcing /Users/me/.vim/ftbundle/python/jedi-vim/./autoload/jedi.vim
954.426  617.175  002.586: sourcing /Users/me/.vim/ftbundle/python/jedi-vim/./ftplugin/python/jedi.vim
2947.941  1926.078  1926.078: sourcing /Users/me/.vim/ftbundle/python/jedi-vim/./autoload/jedi.vim
2948.210  1946.837  020.759: sourcing /Users/me/.vim/ftbundle/python/jedi-vim/./ftplugin/python/jedi.vim
757.152  499.467  499.467: sourcing /Users/me/.vim/ftbundle/python/jedi-vim/./autoload/jedi.vim
757.358  500.011  000.544: sourcing /Users/me/.vim/ftbundle/python/jedi-vim/./ftplugin/python/jedi.vim

速度が安定しないけど、酷い時で約 1.95sec 速い時で 500msec かかってる。

一度 Vim を起動して、すぐに再起動すると、もっと速い。

# OSキャッシュにのせないため、マシンを再起動しながらやったから時間かかった…


いつからこんな遅くなったのかというのを調べてみた。

jedi-vim 0.7 jedi 0.7

108.692  000.672  000.672: sourcing /Users/me/.vim/ftbundle/python/jedi-vim/./ftplugin/python/jedi.vim
172.864  000.078  000.078: sourcing /Users/s_ohyanagi/.vim/ftbundle/python/jedi-vim/./after/./ftplugin/python/jedi.vim

速い。ただし補完の候補が出てくるのが遅い(最新は0.7 に比べると速い)。


jedi-vim 0.8 jedi 0.8

1255.951  984.472  984.472: sourcing /Users/me/.vim/ftbundle/python/jedi-vim/./autoload/jedi.vim
1256.185  1009.923  026.451: sourcing /Users/me/.vim/ftbundle/python/jedi-vim/./ftplugin/python/jedi.vim

どうやら 0.8 から遅くなった。


ソースコードを見ていると、0.7 に比べて jedi 自体がファイル数が増えている。

最新の jedi 0.9 になるともっと増えている。

プロファイル取るために全てのコードにデバッグコードを仕込んだわけではないが、おそらく Python のファイルの import に時間が掛かっている模様。


インポートが遅いのは Python 自体を速くする、マシンの性能を速くする、ファイル数を減らすなどをしないと無理なので、ちょっと簡単に手を出せない。


次に試したのが、Vim-Plug による lazy load。

Vim-Plug とかそこら辺のプラグインマネージャ系は lazy load があるそうなので試してみた。

結果は対して変らない。

Lazy load ってのが filetype が python の時に読み込むというのが lazy load やら on demand load という事ならそらそうだ。

もともと unbundle.vim を使って filetype ごとのロードしかしていない。

# 余談だけど、vimrc とかに読み込むプラグイン候補をいちいち書くの、めんどくさかった


どうしたもんかと思っていたら、Vim 自体のパッケージ機能は使えないかと思ったら、タイムリーに

vim pluginのbackground loading - daisuzu's notes を読んだ。


MacVim Kaoriya(7.4 1-1537) にはまだタイマー機能は入っていなかったが、 Vimのパッケージ機能を試してみました - Blank File にある遅延読み込みをすれば出来るんじゃないかと思い、試してみた。

" ~/.vim/config/plugins.vim に定義
function! LazyLoadPython() abort
  packadd jedi-vim
  packadd pyflakes-vim
  packadd vim-autopep8
  packadd vim-virtualenv
  set filetype=python
  autocmd! lazy_load_python
endfunction

" .vimrc に定義
autocmd VimEnter * source ~/.vim/config/plugins.vim
augroup lazy_load_python
  autocmd! InsertEnter *.py :call LazyLoadPython()
  autocmd Filetype python    setlocal omnifunc=jedi#completions
augroup END

タイマー機能でバックグラウンドで読み込みたいが、まだできないので、とりあえず初回のインサートモードに入った際に実行するようにした。

これで Vim の起動速度が以下の感じになった。

jedi-vim 0.7 191.565
jedi-vim 0.8 482.318
jedi-vim 0.91015.580
pack178.358

ただこれでもインサートモードの最初は遅いので、早くタイマー機能使ってバックグラウンドで読み込みさせたい。

Pack について

Pack については、Vimのパッケージ機能を試してみました - Blank File にあるとおり、ハマりどころがある。

完全に stable になっているわけではないし、変更が色々入る可能性が高い。

ただ packadd したらプラグインが有効になるという設計は素晴らしいと思った。

タイマー機能が安定して使えるようになれば、完全にバックグラウンドで遅延読み込みができる。


vimrc などに読み込むパッケージを定義しなくて良いのも素晴らしい。

自分は気になるプラグインgit clone して Vim を起動するだけにしたいので、自分の使い方にもあっている。

2016-03-18

[] Browserify と fs モジュール

Electron で Watchify(Browserify)使ってると、fs モジュールが使えないという現象にあたった。


今遊んでいる環境が、Gulp で babel と wathify で Electron なアプリケーションを差分コンパイルしながらのため、brfs を使えばいけるとか、ググったら色々でてくるが、gulp で brfs を transform に入れてもだめだった。

const fs = window.require('fs');

とかだと行けた。


が、window とかつけなくても Electron で Babel を使った Markdownアプリケーションとかあるので、絶対いけるはずと、ググったら、node-webkitでもbrowserify使いたいしnodeのrequireも使いたい - Qiita がヒットした。

gulp.task('compile:js', $.watchify((watchify) => {
  var b = watchify({
    watch: watching,
    verbose: true,
    debug: true,
    ignoreMissing: true,     // これ
    detectGlobals: false,     // これ
    bare: true,
    transform: ['babelify', 'envify'],
    builtins: [],                   // これ
  });

  ['boot.js'].forEach((f) => {
    gulp.src(SRC_DIR + f)
      .pipe($.plumber())
      .pipe(b)
      .pipe(buffer())
      .pipe($.sourcemaps.init({ loadMaps: true }))
      .pipe($.if(DEBUG, $.uglify({ compress: true })))
      .pipe($.if(DEBUG, $.sourcemaps.write()))
      .pipe(gulp.dest(DIST_DIR));
  });
}));

上記のように watchify の定義の順番を変えてやればいけた。


なお環境は

    "babel-core": "^6.5.2",
    "babel-plugin-react-transform": "^2.0.0",
    "babel-preset-es2015": "^6.5.0",
    "babel-preset-react": "^6.5.0",
    "babelify": "^7.2.0",
    "electron-prebuilt": "^0.36.9",
    "watchify": "^3.7.0"

な環境。

2016-02-20

[] SQLAlchemy との戦い

ここ数ヶ月 SQLAlchemy を使って開発をしている。


開発当初は特に問題もなく調子よく行っていたが、自分のローカル環境の MySQL の設定を本番に近づけたため、 SQLAlchemy がエラーをはいた - Memo が出た。

pool_recycle の値を短くすれば解決と思ったが、解決しなかった。

前提として FW は Flask を利用。SQLAlchemy は素で scoped_session を使っている。

Flask-SQLAlchey や Flask-Alchemy-Session は使ってない。

autocommit, autoflush は False の設定。


でたエラーが

sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (2013, 'Lost connection to MySQL server during query') 

だったり

sqlalchemy.exc.StatementError: (sqlalchemy.exc.InvalidRequestError) Can't reconnect until invalid transaction is rolled back 

だったり。


現象としては、一つのページにとどまっていて、wait_timeout で設定した 60 秒を超えて、ページをリロード(MySQL にアクセス)した際にでる。

# 更新系は出ない


とりあえずぐぐって、出てきたページを片っ端から試してみた。

SQLAlchemyの OperationalError: MySQL Connection not available エラー - Life is Really Short, Have Your Life!!

コメント欄にあるように、session.remove() をリクエストの最後に呼んでやる。

これは元々 Flask の teardown_request で呼んでいる。

効果無し。


FlaskとSQLAlchemyを使っててMySQL server has gone awayってなる - petitviolet_blog

SQLAlchemy+MySQLで「Lost connection to MySQL server during query」|python|サムライファクトリー開発ブログ

にあるように、Pool event を使って、MySQL リクエストごとに ping を飛ばす。

Connection Pooling — SQLAlchemy 1.1 Documentation

効果無し。


Session.removeを呼ぶタイミング - スコトプリゴニエフスク通信

@soundkitchen さんに教えて貰った。

リクエストの最後は漏れる可能性があるから、リクエストの最初に remove() してセッションを作り直してみたら? というもの。

効果なし。


更新系ではでなくて、参照系で出るのはなにか違いがあるのかと思って、コードを見直したら、session.commit() しているかしていないかの違いがあった。

これかと思って、試してみたら、正解だった。


これで万事解決したかと思ったが、依然でる所があった。

因みにでたのが、WTForms で SQLAlchemy からセレクトボックスに DB の項目を出している所だった。


こんな感じで、セッションのクローズ漏れが出てくるのが怖い。

SQLAlchemy — The Pyramid Community Cookbook v0.2

Pyramid のドキュメントではリクエストコンテキストの最後に、必ず commit, close をしていた。

これにならって、以下のようにした。

@app.teardown_appcontext
def session_clear(exception):
    if exception and Session.is_active:
        session.rollback()
    else:
        #session.commit() 最初こうしてたけど、意図して Rollback した際に不具合おきるからなにもしない
        pass
    Session.close()

あと、session 生成時に、session.expire_on_commit = False しておかないと、例外が起きた。

DetachedInstanceError: Instance <MyModel at 0x36bb190> is not bound to a Session;
attribute refresh operation cannot proceed

なので、sessionmaker の際に expire_on_commit を付けるようにした。


とりあえずこれで安定して動くようになった。

2016-01-11

[] WTForms で QuerySelectMultipleField に default を設定する

WTFormsのSelectFieldで選択値(selected)を保持する方法 - Life is Really Short, Have Your Life!!

WTForms で Select 関連は default を外部から設定できない。

SelectField や QuerySelectField は上記にある。

QuerySelectMultipleField で DB から読み込んだ値をセレクトボックスで複数をデフォルトで設定したい場合は以下のようにすれば行けた。

# -*- coding: utf-8 -*-
from wtforms.ext.sqlalchemy.fields import QuerySelectMultipleField as Base

    """
    QuerySelectMultipleField can only add `default` value at constructor.

    If you want to do like following, you should use this class.

    .. code-block:: python
        form = MyForm(**values)
        #: `users` is `QuerySelectMultipleField` value.
        #: Rows is SQLAlchemy's row list.
        form.users.default = ([u.users.id for u in rows])
    """
    def iter_choices(self):
        if self.allow_blank:
            yield ('', self.blank_text, self.data is None)

        for pk, obj in self._get_object_list():
            if obj.id in self.default:
                yield (pk, self.get_label(obj), True)
            else:
                yield (pk, self.get_label(obj), False)

2016-01-08

[] SQLAlchemy がエラーをはいた

Python で Web アプリケーションを作っていて、ログイン後しばらく放っておいて、他の画面に遷移すると以下のようなエラーが出た。

sqlalchemy.exc.StatementError: (sqlalchemy.exc.InvalidRequestError) Can't reconn
ect until invalid transaction is rolled back 

今まで SQLAlchemy を使ってたけど、初めて遭遇した。

なんだろうこれと思って調べたら、SQLAlchemy の pool_recycle の秒数が MySQL の wait_timeout の秒数より長く設定していためだった。

とりあず pool_recycle の値を wait_timeout と同等にする事で解決。


ちゃんと MySQLチューニングをする際に気をつけないと。

2015-12-31

[] 2015 年振り返り

f:id:heavenshell:20151231132914p:image:w460

OSS480

全て Public Contribution で、今年前半は Pythonbot フレームワークを書いていたから、コミット数が多い。

4ヶ月くらいで、ほぼ機能を作り終えたので、激減してるのが分かる。

思いつきで作り始めたけど、Blinker 使ってイベントを中心に動くようにしたり、割と奇麗に書けたかなーと自画自賛してる。

まぁ自分しか使ってないので、アレだけど。


この bot フレームワークは自社の Slack の一部のチャンネルで元気に動いてるが、Google Image 検索の API が完全に止まったので、Google Custom Search Engine に乗り換えて、制約が厳しいのが辛い。

もっと緩い制約の画像検索が欲しい。


他の OSS の活動としては、細々とメンテしている JSDoc.vimGitHub の star がようやく 100 を超えた。

定期的に Issue が来て、自分以外の人が答えてくれたりするようになった。

またドキュメント周りの拙い英語を直してくれたり、バグフィックしてくれたりと大変ありがたい。

個人的には今の機能で満足しているので、ドラスティックに変更を入れる事はあんまり考えてないが、面白い提案があったら、入れてみるかーくらいの感じ。


他人のプロダクトにコミットしたのは、Lua の Virtualenv みたいなのが上手く動かなかったので、Pull Request 投げた(取り込んでくれてない)、Python の ORM に軽い Pull Request 出した(取り込んでくれた)くらいで、あんまり出来てない。


新しい事

Lua を少し書いてみた。Lua の文法だったり、エコシステムだったり、テスト周りだったりを知れたのは良かった。


Golang でツールを書いた。主に仕事で使う(でもあんま使わなかった)と想定したものだった。もう少しさくっと作れるようになりたい。


仕事で JavaJVMチューニング周りをやった。

だいぶ苦労したけど、半年以上全く問題なく動いていて少し自信がついた。


完全にプライベートで GitHub にもあげてないけど、ES6 と React.jsハイブリッドアプリを作ってみようと始めた。

ようやく React.js がほんの少しだけ分かって来た。

まだ React.js で何も考えずに props でバケツリレーをやってる。

ここら辺が辛くなってきたら、Flux を考えるんだろうけど、Flux に関しては落ち着いた起こして下さいという感じ。

ハイブリッドアプリということで、モバイルフレームワークを色々試した。

OnsenUI も良さげだったが、もっと色々そろっている Framework7 をメインにさわっていて、とても良い感じがしている。

OnsenUI は Ver2 から Angular の依存が一部しかなくなったのが素晴らしいが、ログインスクリーンとか、アクションシートとかそういう物が欲しい。

CSS 頑張ればいいんだろうけど、頑張りたくないので、デフォルトでついて欲しい…。


仕事で CoffeeScript を使った。ES6 を触っていたのもあって、そこまで抵抗なくコード読めるし、書けた。

その他

10 月以降は主にお客さんの先に行ってお話を聞くという仕事が多くなってたので、コードを書く時間が激減した。

まぁコードはプライベートで書けば良いので何の問題もないが、モチベーションのコントロールが課題。


あと健康面は少し気をつけたい。秋口にしかも連休中に二回も扁桃腺で倒れたので、ストレスとの付き合い方を考えないといけない。

まとめ

それなりにしんどい一年だった。アウトプットが足らなさすぎる。

来年はもう少し増やして行きたい。