- 投稿日:2019-04-05T22:53:39+09:00
プログラミングとはなに?小学生に説明してみようと思う
tl;dr
小学校からプログラミングを教えるそうで、2020年からは必修になるとのこと。
わたしの職業がWebエンジニア(プログラマー)ということもあって、小学生の子どもから「プログラミングとはなにか?」と聞かれるのですが、うまい説明が見つからない…
ということでまとめてみました。
プログラミングってなに?
プログラミングは英語で書くと「Programming」。
Program + ingということです。現在進行形なので、無理やり訳すと「プログラムをしている状態」とか「プログラム作ること」となりますね。
プログラムってなに?
では「プログラム」ってなんなのか?
辞書で引いてみるとこんなふうに載っていました。
- 物事の予定。行事の進行についての計画。
- 映画・演劇・コンサートなどの演目や曲目,あらすじや解説などを書いた表や小冊子。
- コンピューターに,情報処理を行うための動作手順を指定するもの。また,それを作成すること。 (三省堂 大辞林より)
もともとは「予定」とか「計画」の意味だったのものが、コンピューター用語に転用されてコンピューターに「動作手順」を指定するものになったということです。
コンピューターの「動作手順」とは?
動作手順というのはなにかというと、コンピューターに対して
「どのように動くのか」
を命令(指示)するものです。
たとえば、コンピューターの画面にあいさつを表示させたいとすると
puts "Hello World!!"と書きます。
こうすると画面には
Hello World!!と表示されます(ここに書いたのはRubyという言語(言葉)で書いていますが、他にもいろいろな言語があります)。
こういった命令を組みあせて、コンピューターに様々な動きをさせるのがプログラミングです。
終わりに
これで小学生の子どもに理解できるだろうか…?
- 投稿日:2019-04-05T21:47:32+09:00
世界初? "アイテムで繋がるSNS" を作ってみた。 ~顔やステータスよりも,持ち物で勝負する~
"アイテムで繋がるSNS"を初心者が2ヶ月で作って見ました
個人間で互いにレンタルができる匿名CtoCサービス(無料)があったら面白そうだと思い作りました。将来、企業への広告媒体としてのモデル(下ネタ以外でも)の可能性があるのかなと。作ってきた経緯等を此方の記事に書きたいと思います。ありそうでなかった?(無知でしたらすいません↓)個人的に"SSS(ソーシャル-シェアリング-システム)"と名付けました。
Ruby on Railsで作り、後ろでどんな技術が使ったのか簡単に解説します。
そして、少しエロいです(笑)
- Add-Item 機能
- 武器を買う
商品購入ページ or ECサイトへ...等をGoogle先生を調べながら実装しました。
URL
Den-appスペック
使用言語等など
- Ruby 2.5.1
- Rails 5.2.1
- Vagrant CentOS7
著者プロフィール
- プログラミング歴 約1年
(去年の 5月13日〜14日位から)- Rubyのチェリー本 => Rails チュートリアル => クローンアプリ制作後,実際に自分の考えたアプリを作ろうと思い, "Den-app"を制作しました。
- PG,転職探し中
- 祝 初サイト
- メッセージ機能(リアルタイム). ~ Action Cable ~
- ここからは実際に使用した技術を2種類紹介いたします。
どちらも, Rails5.2系で標準なので詳しい素晴らしい記事は他に多くあります。- "den-app"にどのような効果をもたらしたかを書きたいと思います。
1つ目は、個人間でのメッセージ機能をページ推移なしで行えるように,"ActionCable"を使用しました。これを使えば、非同期でサーバーと通信が出来ます。
ActionCableは日本語のページ等も多く、技術自体は簡単に実装できたのですが、理想の挙動にするのが私の力不足で時間が掛かり、1ヶ月程掛かりました。
QItaは勿論の事、意外と英語用のプログラミング質問サイト等にサラッと金言が載ってたりして血眼で探した記憶があります。
こういう非同期で通信することを"Ajax化"と呼ぶそうで、ActionCableではないですがden-appではもう一箇所この技術を利用している箇所があります。
- 借りたい申請
皆さんの持ち物に"借りたい”という申請をするページですが、"Ajax化"で、更新せずとも Create & Destroyアクションを送信できます。素晴らしいですね
- 画像アップロード. ~ Active storoge ~
Railsで画像アップロードと聞くと、現在でもやはり,CarrierWaveが人気のようです。探して見ても圧倒的に情報が載って居ました。
Den-appではrails標準の最近追加された,"Active storoge" とストレージにAWSのS3を用いています。採用理由としては、
- 新しい技術好き
- gem追加が要らない
- 最近の紹介ページが意外と多い(2018年頃~)
名前が格好良い(笑)からです。Modelに
user.rbclass User < ApplicationRecord has_one_attached :image .... endとてもシンプルで使いやすいです。
S3との相性?も良いらしく、バケットを追加後アップデート出来るようになりました。CarrierWaveを使っている人も一度使って見ては如何でしょうか?
画像リサイズやデフォルトイメージ(初期画像)も調べればできます。
- プロフィール編集機能
や
- Add-Item 機能で"Active Stroge"は使用されています。クローンアプリを練習した時、CarrierWaveを使用したのですが、現代の段階では標準装備がActive Storageよりも多く便利です。私としても使い分けて使用していきたいと思います。本番へのデプロイ
Railsから繋げる本番環境としてはHerokuを使用しています。Pushコマンド1つで更新できるのはとても魅力がありますね。
ただ、現在直面している問題がネイキッドドメイン(www無し)からDen-app(www有り)へのリダイレクトが出来ない。。。Railsの機能として,config/production.rbRails.application.configure do ... # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. config.force_ssl = true .... endでSSHへのリダイレクトや,○○○..herokuapp.comから独自ドメインへのリダイレクトは設定できたが、ネイキッドドメインからのアクセスが未だに出来ない↓
今後とも勉強していきます。優しい人、教えて下さい。総括
纏めて見ましたが、一言で言うと,"Ruby on Railsは凄まじい"に尽きます。
私のような初心者でも、完璧ではなくともアイデアをサービスとして開発できました。今後とも、Den-appの研磨は勿論のこと、色々作りたいサービスがあるので影でひっそりと一人で作っていきたいと思います。都内で週末などに、一緒にプログラミングをできる方がいればな〜と思っています。(気軽にコメントやTwitterのDM等で連絡くれると嬉しいです。)
Den-appに少しでも興味を持った方は是非登録して見て下さいね。
詳しい使い方は"HowToUse"に載っています。素敵な事があるかもしれません。記事を読んで頂き、大変感謝いたします。
Den-app
- 投稿日:2019-04-05T21:47:32+09:00
世界初? 一人でひっそりと "アイテムで繋がるSNS" を作ってみた。 ~顔やステータスよりも,持ち物で勝負する~
"アイテムで繋がるSNS"を初心者が2ヶ月で作って見ました
個人間で互いにレンタルができる匿名CtoCサービス(無料)があったら面白そうだと思い作りました。将来、企業への広告媒体としてのモデル(下ネタ以外でも)の可能性があるのかなと。作ってきた経緯等を此方の記事に書きたいと思います。ありそうでなかった?(無知でしたらすいません↓)個人的に"SSS(ソーシャル-シェアリング-システム)"と名付けました。
Ruby on Railsで作り、後ろでどんな技術が使ったのか簡単に解説します。
そして、少しエロいです(笑)
- Add-Item 機能
- 武器を買う
商品購入ページ or ECサイトへ...等をGoogle先生を調べながら実装しました。
URL
Den-appスペック
使用言語等など
- Ruby 2.5.1
- Rails 5.2.1
- Vagrant CentOS7
著者プロフィール
- プログラミング歴 約1年
(去年の 5月13日〜14日位から)- Rubyのチェリー本 => Rails チュートリアル => クローンアプリ制作後,実際に自分の考えたアプリを作ろうと思い, "Den-app"を制作しました。
- PG,転職探し中
- 祝 初サイト
- メッセージ機能(リアルタイム). ~ Action Cable ~
- ここからは実際に使用した技術を2種類紹介いたします。
どちらも, Rails5.2系で標準なので詳しい素晴らしい記事は他に多くあります。- "den-app"にどのような効果をもたらしたかを書きたいと思います。
1つ目は、個人間でのメッセージ機能をページ推移なしで行えるように,"ActionCable"を使用しました。これを使えば、非同期でサーバーと通信が出来ます。
ActionCableは日本語のページ等も多く、技術自体は簡単に実装できたのですが、理想の挙動にするのが私の力不足で時間が掛かり、1ヶ月程掛かりました。
QItaは勿論の事、意外と英語用のプログラミング質問サイト等にサラッと金言が載ってたりして血眼で探した記憶があります。
こういう非同期で通信することを"Ajax化"と呼ぶそうで、ActionCableではないですがden-appではもう一箇所この技術を利用している箇所があります。
- 借りたい申請
皆さんの持ち物に"借りたい”という申請をするページですが、"Ajax化"で、更新せずとも Create & Destroyアクションを送信できます。素晴らしいですね
- 画像アップロード. ~ Active storoge ~
Railsで画像アップロードと聞くと、現在でもやはり,CarrierWaveが人気のようです。探して見ても圧倒的に情報が載って居ました。
Den-appではrails標準の最近追加された,"Active storoge" とストレージにAWSのS3を用いています。採用理由としては、
- 新しい技術好き
- gem追加が要らない
- 最近の紹介ページが意外と多い(2018年頃~)
名前が格好良い(笑)からです。Modelに
user.rbclass User < ApplicationRecord has_one_attached :image .... endとてもシンプルで使いやすいです。
S3との相性?も良いらしく、バケットを追加後アップデート出来るようになりました。CarrierWaveを使っている人も一度使って見ては如何でしょうか?
画像リサイズやデフォルトイメージ(初期画像)も調べればできます。
- プロフィール編集機能
や
- Add-Item 機能で"Active Stroge"は使用されています。クローンアプリを練習した時、CarrierWaveを使用したのですが、現代の段階では標準装備がActive Storageよりも多く便利です。私としても使い分けて使用していきたいと思います。本番へのデプロイ
Railsから繋げる本番環境としてはHerokuを使用しています。Pushコマンド1つで更新できるのはとても魅力がありますね。
ただ、現在直面している問題がネイキッドドメイン(www無し)からDen-app(www有り)へのリダイレクトが出来ない。。。Railsの機能として,config/production.rbRails.application.configure do ... # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. config.force_ssl = true .... endでSSHへのリダイレクトや,○○○..herokuapp.comから独自ドメインへのリダイレクトは設定できたが、ネイキッドドメインからのアクセスが未だに出来ない↓
今後とも勉強していきます。優しい人、教えて下さい。総括
纏めて見ましたが、一言で言うと,"Ruby on Railsは凄まじい"に尽きます。
私のような初心者でも、完璧ではなくともアイデアをサービスとして開発できました。今後とも、Den-appの研磨は勿論のこと、色々作りたいサービスがあるので影でひっそりと一人で作っていきたいと思います。都内で週末などに、一緒にプログラミングをできる方がいればな〜と思っています。(気軽にコメントやTwitterのDM等で連絡くれると嬉しいです。)
Den-appに少しでも興味を持った方は是非登録して見て下さいね。
詳しい使い方は"HowToUse"に載っています。素敵な事があるかもしれません。記事を読んで頂き、大変感謝いたします。
Den-app
- 投稿日:2019-04-05T20:14:55+09:00
Sidekiqのソース読んでみたメモ
sidekiqのソースを読んでみた
- sidekiqを使ってるけどなかってどうなってるの?というのを調べることがあったのでまとめ
前提
- バージョン
- v5.2.5
- 読んでみたタイミング
- 2019/03
- ここで書かないこと
- siekiqの使い方
- Thread周り
- 基本的なとこだけ読んでます
- 読んでみたなので正確ではないこともあります。。
クライアント側(jobを積むほう)
Sidekiq::Worker
- 実行する処理とかを用意するクラスにincludeするモジュール
- includeしたクラスにperform_asyncとかが特異メソッドとして追加される
#perform_async
- 中でSidekiq::Clientのオブジェクトを生成してpushを読んでる
Sidekiq::Client
- redisにjobをつむところの役割をやってる
#push
- workerに渡す引数+jobのidを用意して整形してatomic_pushに投げる
#atomic_push
- redisのqueuesていうキーにつむjobのキュー名を入れている(タイプがsetなので同じものを入れるときは重複しない)
- redisのqueue:キュー名のキーにjobを追加する
サーバー側
- jobを実行するほう
登場するクラス
- Sidekiq::CLI
- サーバー側の起動を行う
- そのあとは停止などのコマンドを受け付ける
- Sidekiq::Launcher
- このオブジェクトのrunを呼べばサーバー側が動く
- jobを取ってきて処理するのと、retryやスケジュールからqueueを積む処理を起動してくれるのでランチャー
- Sidekiq::Manager
- Sidekiq::Processorを作成&ぐるぐる実行させる
- Sidekiq::Processor
- 一定時間ごとにqueueをチェックしに行って処理を実行
- こいつがperformを呼び出してる
- Scheduled::Poller
- 即時実行じゃないjobや一度失敗したjpbを確認して、実行タイミングになったらqueueに積む
sidekiq起動のところ
bin/sidekiq
- Sidekiq::CLI.instance
- Sidekiq::CLIのインスタンスを生成(instanceってなんだ?と思ったけどシングルトンだった。。)
- Sidekiq::CLI#parse
- 設定とかをsetしてる
- Sidekiq::CLI#run
- Sidekiq::Launcherのインスタンスを生成&Sidekiq::Launcher#runを呼び出したあとはコマンドを受け付けるためぐるぐるし続ける。
- Sidekiq::Launcher#run
- Sidekiq::Manager#startとSidekiq::Scheduled::Poller#startを呼び出す
- Sidekiq::Manager#start
- Sidekiq::Processor#startを呼び出す
- Sidekiq::Processor#start
- スレッドでrunを実行
- Sidekiq::Processor#run
- jobがあればとってきて該当workerのperformを実行をぐるぐる。
- Sidekiq::Scheduled::Poller#start
- redisのretryとscheduleの中で実行タイミングになったものを取り出してqueueにつむ。
- retryとscheduleはzsetで、timestampをキーにしてて、今の時間よりも小さいものーでとってるっぽい
ざっくりかくと
Sidekiq::CLI -> Sidekiq::Launcherを叩いてあとは入力待ちぐるぐる
Sidekiq::Launcher#run -> Sidekiq::Manager#startとSidekiq::Scheduled::Poller#startを叩く
Sidekiq::Manager#start -> Sidekiq::Processorをぐるぐるさせる
Sidekiq::Scheduled::Poller -> ぐるぐるおまけでRedisに登録されるもの
※ namespaceは省く
キー type 値 schedule zset 予定されたジョブが入っている queues set 一度でもジョブが積まれたキューのリスト stat:processed string 完了したジョブの数 stat:processed:yyyy-mm-dd string 該当日に完了したジョブの数 stat:failed string 失敗したジョブの数 stat:failed:yyyy-mm-dd string 該当日に失敗したジョブの数 queue:キュー名 list そのキューの待機状態のジョブのリスト limit:processes set TODO (limit:heartbeatのUUIDが入っていた。。) limit: heartbeat:UUID string TODO (trueとか入ってる。。) host:port:xxxx:workers hash そのworkerが現在実行中のジョブの情報 感想
- ランチャーってなんぞと思ったけど、一括で必要なのを起動してくれるもののことらしいと今回知った。。
- Threadとか詳しくないのでいつかちゃんと触って理解しよう。。
- redisの型とか全然知らなかったのでちょっと知れてよかった。
- gemを読んでみたって記事はありなのか。。?(お勉強メモなのでお許しください。。)
- 顔文字とか絵文字のメソッドがちょいちょいあって面白かった
- 投稿日:2019-04-05T16:58:35+09:00
【Rails/JavaScript】Googleマップ上にある同じ座標の複数マーカーをずらす
個人開発のWebアプリ「まちかどルート」v5.62への実装メモです。
Googleマップのマーカーが重なる
まちかどルートでは位置情報付きの投稿をすべて「ルートマップ」と名付けたGoogleマップ上にマーカー表示させています。
ちなみにgmaps4railsやgeocoderというgemを使っています。
使い方についてはこちらの記事などを参考にしました。
しかしながらまったく同じ座標の投稿があると複数のマーカーが重なってしまい、とても不便です。
maxRandomDistanceを使う
handler = Gmaps.build('Google', { markers: { maxRandomDistance: 5 } });viewに使う
handler = Gmaps.build('Google');
の部分を上記のようなコードに変更すると、同じ座標にある複数のマーカーを下の写真のようにずらして表示できるようになります。ランダムにずれるので細かな調整には不向きですが、簡単な対応策として良いのではないでしょうか。
- 投稿日:2019-04-05T15:27:28+09:00
Railsで「CarrierWave+fog」を使ってAWSのS3へアップロード
はじめに
Railsアプリを外部サービスと連携させてみようと思い、AWSが提供するS3へ画像や動画をアップロードしてみた過程をまとめていきます。
今回はCarrierWaveで単純にアップロード機能を利用し、fogでクラウド上のs3へ簡易的にアップロードできるようにしています。AWS上での設定
S3の詳しい利用法の要点をまとめます。
①AWSアカウントの取得
AWSのアカウントを取得。原則としてクレジットカードの登録が必要になります。アカウント作成から12か月間は無料枠(最大5GB)としてS3が利用できます。②IAMユーザーの作成
続いてIAMユーザーを作成します。IAMとはAWS上のサービスを操作するユーザーと、ユーザーアクセス権限を管理するものです。
IAMがなかった場合、フルアクセスのAWSアカウントをみんなで共有することになります。
ユーザーがアクセスするための認証情報(アクセスキー、ポリシーの設定)を取得しS3へアクセスします。※ここで取得したアクセスキーIDとシークレットアクセスキーは絶対に控える&定期的に更新する
③バケットの作成
バケットとは、ファイルなどのオブジェクトを格納するための入れ物です。ここでバケット名を決めるのですが、注意が必要です。AWSバケット名の命名規則に従った名前を付けてください。https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/BucketRestrictions.html
自分の場合、バケット名に不適切な文字を記述してしまい、原因がわからずS3にいつまでたってもアクセスできない事態に見舞われました。続いてバケットのリージョンを設定します。リージョンとはS3のサービスが提供されている場所を意味します。「アジア・パシフィック(東京)」の場合、使用する資格情報は「ap-northeast-1」となります。
CarrierWaveの導入
gem 'carrierwave' gem 'fog'アップロードファイルの作成
$ rails g uploader file
app/uploaders/file_uploader.rbが生成されます。file_uploader.rbclass FileUploader < CarrierWave::Base require 'streamio-ffmpeg' if Rails.env.production? storage :fog else storage :file end def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end def extension_white_list %w(jpg jpeg gif mp4 MOV wmv) end version :screenshot do process :screenshot def full_filename (for_file = model.logo.file) "screenshot.jpg" end end def screenshot tmpfile = File.join(File.dirname(current_path), "tmpfile") File.rename(current_path, tmpfile) movie = FFMPEG::Movie.new(tmpfile) movie.screenshot(current_path + ".jpg", { resolution: '500x600' },preserve_aspect_ratio: :width) File.rename(current_path + ".jpg", current_path) File.delete(tmpfile) end endサムネイル用にffmpegを使用しています。
今回はアップロードされたファイルが画像でも動画でも、一旦tmpフォルダーにjpgファイルとして格納し、最終保存先を開発環境時はローカルに、本番環境時はfogに設定しています。続いてcarrierwave.rbを作成します。
carrierwave.rbrequire 'carrierwave/storeage/fog' CarrierWave.configure do |config| config.fog_provider = 'fog/aws' config.fog_credentials = { provider: 'AWS', aws_access_key_id: 'アクセスキー', aws_secret_access_key: 'シークレットアクセスキー', region: 'リージョン', } config.fog_directory = 'バケット名' #日本語ファイル名の設定 CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/ config.cache_dir = "#{Rails.root}/tmp/uploads" endモデルにuploaderを紐づけます。
gallery.rbclass Gallery < ApplicationRecord mount_uploader :file, FileUploaderviewの実装は今回は割愛します。
これで本番環境時にS3にアップロードすることができました。おわり
特に基盤となってくるuploaderとcarrierwaveの設定はまだ完全に理解していないし、不十分なところもあると思うので今後さらに深堀していきたいです。
参考
・https://qiita.com/junara/items/1899f23c091bcee3b058
・http://vdeep.net/rubyonrails-carrierwave-s3
・https://qiita.com/yukiyukimiyaiwa/items/a9e0103c8342b81f6ac1
・https://stackoverflow.com/questions/15717368/uploading-image-to-s3-using-carrierwave-and-fogs-bad-uri
- 投稿日:2019-04-05T13:23:54+09:00
Mac(10.4 Mojave)で gem install scrypt に失敗(ld: symbol(s) not found for architecture i386)
Macを10.4(Mojave)にアップデートし、Xcode Command Line Tools(macOS 10.14) for Xcode 10.2をインストールしたあと、
gem install scrypt
の実行に失敗しました。$ gem install scrypt -v 2.0.2 Building native extensions. This could take a while... ERROR: Error installing scrypt: ERROR: Failed to build gem native extension. ... gcc -bundle -o x86_64-darwin/libscrypt_ext.bundle x86_64-darwin/crypto_scrypt-sse.o x86_64-darwin/memlimit.o x86_64-darwin/scrypt_calibrate.o x86_64-darwin/scrypt_ext.o x86_64-darwin/scryptenc_cpuperf.o x86_64-darwin/sha256.o -fexceptions -arch x86_64 -arch i386 ld: warning: The i386 architecture is deprecated for macOS (remove from the Xcode build setting: ARCHS) ld: warning: ignoring file /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/lib/libSystem.tbd, missing required architecture i386 in file /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/lib/libSystem.tbd Undefined symbols for architecture i386: ...文字通り10.4(Mojave)向けのCommand Line Toolsから
-arch i386
をサポートしなくなったことが原因らしい。
https://developer.apple.com/download/more/ から Xcode Command Line Tools(macOS 10.13) for Xcode 10.1 をインストールしたら、gem install scrypt
できるようにしました。
(ただ根本的な解決にはなっていないので、要調査)
- 投稿日:2019-04-05T03:20:15+09:00
UTF-8のコードポイントはどうやって高速に数えるか
UTF-8文字列からコードポイント数を計算するアルゴリズムについて紹介します。コードポイント数カウントは、シンプルに書くのはそれほど難しくないものの、高効率な実装は意外にややこしいです。
内容は二本立てです。
- 実践的な実装について、Ruby(CRuby)の内部実装(string.c)で使われているものを紹介します。
- 標準Cの範囲を超えて、SIMD命令(AVX/AVX2)を使った実装についても述べます
- 軽く検索する限りだと既知のアルゴリズムが見当たらなかったので、アドホックな実装をひねり出しましたが、そんなに効率は悪くなさそうです
おまけで簡単な性能評価をやってみました。
なお、UTF-8文字列はバリデーション済み(不正なシーケンスでないことが分かっている)であるとします。Rubyの内部実装だとどうやっているか
まずは、それがコードポイントの先頭バイト(leading byte)かを判定する
is_utf8_lead_byte
を定義します。正体はプリプロセッサマクロです。#define is_utf8_lead_byte(c) (((c)&0xC0) != 0x80)
https://github.com/ruby/ruby/blob/v2_6_2/string.c#L1629
0xC0
つまり0b11000000
とマスクすると、上2ビットだけが残ります。UTF-8の先頭でないバイトは0b10xxxxxx
という並びになっているので、もし非先頭バイトであればマスク後には0b10000000
となりますが、これはつまり比較演算!= 0x80
で先頭バイトか判定できることを意味します。このマクロを使うと、簡単にコードポイントカウントを実装できます。
#define is_utf8_lead_byte(c) (((c)&0xC0) != 0x80) int64_t count_utf8_codepoint(const char *p, const char *e) { while (p < e) { if (is_utf8_lead_byte(*p)) len++; p++; } return len; }さて、ここまでは単純ですが、Ruby処理系における実装はもう少し工夫がしてあります。
上記の素朴な実装ではバイト単位で文字列処理をしていますが、現代のPC・サーバなどの典型的な環境であれば32/64ビット整数を不自由なく扱えるため、相対的に非効率です。
そこで、複数バイトをuintptr_t
型でまとめて読み込み、含まれる全ての先頭バイトの数を一気に数えるということをします。コードポイント数は先頭バイト数に等しい ので、コードポイントを数えるには先頭バイトを数えればよいのです。#define NONASCII_MASK UINT64_C(0x8080808080808080) static inline uintptr_t count_utf8_lead_bytes_with_word(const uintptr_t *s) { uintptr_t d = *s; d = (d>>6) | (~d>>7); d &= NONASCII_MASK >> 7; return __builtin_popcountll(d); }https://github.com/ruby/ruby/blob/v2_6_2/string.c#L1631-L1665
※ (インクルードヘッダを除き)コード片のみでコンパイルできるように少し書き換えています。また、
sizeof(uintptr_t) == 8
の環境を前提に記述しています。まとめて計算してるのでちょっとややこしいですが、次の点に注意してください。
NONASCII_MASK >> 7
は各バイトの最下位ビットだけが立っているマスク
d
はこれとbit andされるので、最下位ビットのみが残る(d>>6) | (~d>>7)
の各バイト最下位ビットに着目すると「7ビット目が立っている or 8ビット目が落ちている」判定になっている
- 7ビット目が立っている:UTF-8の先頭でないバイトは、常に
0b10xxxxxx
という並びになっていることを思い出すと、つまり先頭バイト- 8ビット目が落ちている:ASCIIなので先頭バイト
ビット演算後の
d
は、各バイトについて最下位ビットが立っていると先頭バイトだということを表すようになります。その他のビットはマスクされているので常に0です。したがって、含まれる先頭バイトの数を数えるには、popcntをすればいいというわけです。最後にようやくですが、
count_utf8_lead_bytes_with_word
を使った実装の全体像を示します。int64_t ruby_count_utf8_codepoint(const char *p, const char *e) { uintptr_t len = 0; if ((int)sizeof(uintptr_t) * 2 < e - p) { const uintptr_t *s, *t; const uintptr_t lowbits = sizeof(uintptr_t) - 1; s = (const uintptr_t*)(~lowbits & ((uintptr_t)p + lowbits)); t = (const uintptr_t*)(~lowbits & (uintptr_t)e); while (p < (const char *)s) { if (is_utf8_lead_byte(*p)) len++; p++; } while (s < t) { len += count_utf8_lead_bytes_with_word(s); s++; } p = (const char *)s; } while (p < e) { if (is_utf8_lead_byte(*p)) len++; p++; } return (long)len; }https://github.com/ruby/ruby/blob/v2_6_2/string.c#L1680-L1700
なんでこんなに複雑な実装なんだという話ですが、
count_utf8_lead_bytes_with_word
を正しく動作させるためには、uintptr_t
のnバイト境界(典型的にはn = 4 or 8だと思います)にアライメントされたポインタから読み出す必要があるからです。具体的なやり方としては次のようになります。
lowbits
を足してからマスクすることで、p
をアライメントされた位置まで進めてs
とするlowbits
を引いてからマスクすることで、e
をアライメントされた位置まで戻してt
とするこうして前処理された
s
,t
に対してならcount_utf8_lead_bytes_with_word
が使えます。
もちろんs
,t
とp
,e
にはズレが生じるかもしれないで、普通のループ処理を使って前後の差分を埋めています。AVXによるアルゴリズム/実装
アルゴリズム、というほどのものではないので、実装と並行して解説していきます。
基本的な方針としては、UTF-8バリデーションのアルゴリズムと同じく、バイトの上位ニブルを見れば先頭バイトかどうかわかるという性質を使います。vpshufbを使えば先頭バイトのあった位置だけに1を立てることができます。
1の数え方ですが、SIMDでは水平方向に加算するのはコストがかかるので、32バイト単位で切り出されたベクトルをそのまま足し合わせていくことにします。ただし、バイトで表せる値の範囲は0〜255であることから、ベクトル加算は255回を超えて行うとオーバーフローが起こりかねません。
回避策としてはループを分割し、255回ごとに水平加算で値を集約していくことにします(もちろん入力が無くなったら255回以前にループは終了します)。こうすると水平加算は最小で1回、最大でも255回に1回しか実行されないので、相対的にコストが低くなると期待できます。さて実装です。まず、補助関数として、バイト単位で水平加算する関数を定義しておきます。
inline int32_t avx2_horizontal_sum_epi8(__m256i x) { __m256i sumhi = _mm256_unpackhi_epi8(x, _mm256_setzero_si256()); __m256i sumlo = _mm256_unpacklo_epi8(x, _mm256_setzero_si256()); __m256i sum16x16 = _mm256_add_epi16(sumhi, sumlo); __m256i sum16x8 = _mm256_add_epi16(sum16x16, _mm256_permute2x128_si256(sum16x16, sum16x16, 1)); __m256i sum16x4 = _mm256_add_epi16(sum16x8, _mm256_shuffle_epi32(sum16x8, _MM_SHUFFLE(0, 0, 2, 3))); uint64_t tmp = _mm256_extract_epi64(sum16x4, 0); int32_t result = 0; result += (tmp >> 0 ) & 0xffff; result += (tmp >> 16) & 0xffff; result += (tmp >> 32) & 0xffff; result += (tmp >> 48) & 0xffff; return result; }
avx2_horizontal_sum_epi8
を用いると、32の倍数についてコードポイントをカウントする関数は比較的簡単に書けます。int64_t avx_count_utf8_codepoint(const char *p, const char *e) { // `p` must be 32B-aligned pointer p = static_cast<const char *>(__builtin_assume_aligned(p, 32)); const size_t size = e - p; int64_t result = 0; for (size_t i = 0; i + 31 < size;) { __m256i sum = _mm256_setzero_si256(); size_t j = 0; for (; j < 255 * 32 && (i + 31) + j < size; j += 32) { const __m256i table = _mm256_setr_epi8( 1, 1, 1, 1, 1, 1, 1, 1, // .. 0x7 0, 0, 0, 0, // 0x8 .. 0xB 1, 1, 1, 1, // 0xC .. 0xF 1, 1, 1, 1, 1, 1, 1, 1, // .. 0x7 0, 0, 0, 0, // 0x8 .. 0xB 1, 1, 1, 1 // 0xC .. 0xF ); __m256i s = _mm256_load_si256(reinterpret_cast<const __m256i *>(p + i + j)); s = _mm256_and_si256(_mm256_srli_epi16(s, 4), _mm256_set1_epi8(0x0F)); s = _mm256_shuffle_epi8(table, s); sum = _mm256_add_epi8(sum, s); } i += j; result += avx2_horizontal_sum_epi8(sum); } return result; }内側のループでベクトルごとの加算をし、外側のループで加算後のベクトルの要素値を集約して
result
に足していきます。
table
はUTF-8の上位ニブルから先頭バイトを検出し、先頭バイトのみを1にするための変換テーブルです。内側のループの条件式
j < 255 * 32 && (i + 31) + j < size
は少しややこしいです。ループ変数j
は32ずつ加算されるので、条件式の前半j < 255 * 32
はループを255回で打ち切るという意味です。後半の(i + 31) + j < size
は、入力長さsize
を32の倍数を超えてはみ出さないためのガードです。内側のループが終了するとき、i
にj
が加算されるので外側ループのi + 31 < size
も0になり、ループは終了します。コラボ実装
32の倍数からはみ出た部分についてはRuby実装と組み合わせることが可能です。
今回は、はみ出た部分は1バイトずつ処理する実装で埋めてみました。p
が32B境界に整列していない可能性もあるため、ベクトル処理の前に32B境界まで1バイトずつスカラで進める処理も手前に挿入しました。int64_t count_utf8_codepoint(const char *p, const char *e) { int64_t count = 0; #if defined(__AVX2__) // increment `p` to 32B boundary while (((uintptr_t)p % 32) != 0) { if (is_utf8_lead_byte(*p)) count++; p++; } // vectorized count count += avx_count_utf8_codepoint(p, e); p += static_cast<uintptr_t>(e - p) / 32 * 32; #endif while (p < e) { if (is_utf8_lead_byte(*p)) count++; p++; } return count; }性能評価
今回は、長い文字列に対して十分にスループットが出ているか、それは定量的にどの程度かを見ていきます。
性能評価の条件について簡単に述べます。
- 環境: Ubuntu 18.04.1 on VirtualBox on MBP Late 2013
- つまりCPUはHaswell世代
- 文字列のサイズは100MiB
- ちゃんとした文字列ではなく乱数列を使う
- 測定値としてrdtsc命令から取得したクロックサイクル数を使う
- 測定は3回行い、中央の値をとる
- 100MiBだと1回の処理が短すぎるので、同じ処理を100回した区間を測定する
- コンパイラとしてClang 8.0とGCC 7.3.0を使う
- コンパイラオプション(共通):
-O3 -masm=intel --std=c++14 -mavx -mavx2 -Wall -Wextra
測定対象は、紹介したスカラ版と、Ruby版と、コラボ実装の節で述べた
count_utf8_codepoint
(以下AVX版)とします。測定結果
実装 コンパイラ クロックサイクル数(Mclk) スカラ版 GCC 7323 スカラ版 Clang 8793 Ruby版 GCC 4573 Ruby版 Clang 2643 AVX版 GCC 2361 AVX版 Clang 2345 なぜこのような結果になったのか?
全体的な結果としてはスカラ版が最も遅く、AVX版が最も高速になりました。
Ruby実装はGCCとClangで大幅な差が付いていますが、これはClangの自動ベクトル化がうまく効いているからのようです。
GCCの自動ベクトル化は、Ruby版の最も重い場所(while (s < t)
のループ)について効いておらず、そこで大幅な差が付いています。興味深いことに、スカラ版を自動ベクトル化した場合には、GCCの方が効率的なコード生成ができているように見受けられます。Ruby版のアルゴリズムからClangはヒントを得たんでしょうか……測定ミスでなければいいのですが。
同じ処理系(Clang)についてRuby版とAVX版を比較すると、AVXコードを手書きすることで1割ほど高速化できています。
しかしこれはコンパイラの支援を受けることで、比較的たやすくintrinsics手書きの9割程度の性能を得られたとも言えるため、あとはコストパフォーマンスの問題になりそうだと見受けられます。ちなみに実時間としては、AVX版-Clangはおよそ0.838秒でした。つまり12GiB/s程度は出ていることになります。文字列が十分大きいためキャッシュは効いておらず、このMBPに付いているDDR3-1600 2chメモリは理論帯域25.6GiB/sであることを踏まえると、およそ帯域の5割ほどを使えていることになります。シングルスレッドのプログラムとしては十分すぎるような気はします。
まとめ
- UTF-8のコードポイントカウントを題材にして、実践的な実装を紹介しました
- UTF-8バリデーションアルゴリズムを応用したカウントアルゴリズムを示し、文字列が巨大である場合について実用性を検証しました
- コンパイラによって性能差・特性差が大きいことも発見しました
Appendix
ソースコード
https://gist.github.com/saka1/5bdd0f0ad4e498d22258982f6b9699c5
- 投稿日:2019-04-05T03:01:55+09:00
RuboCopの設定に書く相対パスはどこからの相対パスか?
RuboCopのv0.67で確認している話です。以前・以降のバージョンではもしかしたら違うかもしれないので注意してください。
document: https://docs.rubocop.org/en/latest/configuration/
TL;DR
inherit_from
に書く相対パス
inherit_from
が書かれた設定ファイルからの相対パス
Include/Exclude
に書く相対パス
- それが書かれた設定ファイルが
.rubocop
で始まるファイル名の場合
- その設定ファイルからの相対パス
- 設定ファイル名が
.rubocop
で始まらない場合
rubocop
コマンドを実行したディレクトリからの相対パス答えとしては上記で終わりですが、これがどういうケースで問題になりやすいのか(ハマりはやすいのか)、というのも書いているので良かったら最後まで見てください
RuboCopでパスが書ける部分
inherit_from: .rubocop_todo.yml Style/CollectionMethods: Exclude: - 'foo/bar.rb' Include: - 'hoge/huga.rb'
inherit_from
- 共通の設定ファイルなどを継承できる
Include/Exclude
- Copを実行する対象・実行しない対象を宣言できる
RuboCopでは上記の2つにパスを書くことができます。
どちらも絶対パス・相対パスどちらでもかけますが、通常は相対パスになると思います。ここで書く、相対パスはどこから見た相対パスなのか?という記事です。
inherit_fromに書く相対パス
こちらはとてもシンプルです。
inherit_fromに書く相対パスは、inherit_fromが書いてある設定ファイルからの相対パスになります。
.rubocop.ymlinherit_from: '../.rubocop_todo.yml'例えば上記の場合、
.rubocop.yml
の中で.rubocop_todo.yml
を読み込んでいますが、
これは、.rubocop.yml
からみて../
にある.rubocop_todo.yml
を読み込みます。treeでいうと以下のような関係になります。(baseというディレクトリ名は適当です。)
. ├── .rubocop_todo.yml └── base └── .rubocop.ymlInclude/Excludeに書く相対パス
こちらがややこしいです。
Include/Excludeの設定が書かれている設定ファイルの名前によって変わります!
- ファイル名が
.rubocop
で始まる設定ファイルの場合
- その設定ファイルからの相対パス
- 例:
.rubocop.yml
や.rubocop_todo.yml
など- それ以外の設定ファイルの場合
rubocop
コマンドを実行したディレクトリからの相対パス- 例:
.my_rubocop_config.yml
やrubocop.yml
(ドットなし)など具体例
. ├── config │ ├── .my_rubocop_config.yml │ ├── .rubocop_todo.yml │ └── .rubocop.yml └── src └── sample.rb上記のようなディレクトリ構成があるとします。configディレクトリ以下に、3つのrubocopの設定ファイルがあります。
.rubocop.ymlinherit_from: - .my_rubocop_config.yml - .rubocop_todo.yml
.rubocop.yml
の中でほか2つの設定ファイルを読み込みます。
$ rubocop -c config/.rubocop.yml
rubocopコマンドは、ルートディレクトリから実行するとします。上記のような前提とします。
この状態で、3つの設定ファイルそれぞれで、
src/sample.rb
をExcludeする設定を書きたいとします。以下のようになります。
config/.rubocop.yml# config/.rubocop_todo.yml Style/CollectionMethods: Exclude: - '../src/sample.rb' # .rubocopで始まるので、設定ファイルからの相対config/.rubocop_todo.ymlStyle/CollectionMethods: Exclude: - '../src/sample.rb' # .rubocopで始まるので、設定ファイルからの相対config/.my_rubocop_config.ymlStyle/CollectionMethods: Exclude: - 'src/sample.rb' # .rubocopではないので、実行ディレクトリからの相対 # or # - '**/sample.rb'
- ファイル名が
.rubocop
で始まる設定ファイルの場合
- その設定ファイルからの相対パス
- それ以外の設定ファイルの場合
rubocop
コマンドを実行したディレクトリからの相対パスなので、
config/.rubocop.yml
とconfig/.rubocop_todo.yml
では、../src/sample.rb
というように../
でたどる必要があります。
config/.my_rubocop_config.yml
は、rubocopコマンドを実行したディレクトリからの相対パスなので、'src/sample.rb'
や**/sample.rb
とかけます。なので、RuboCopの設定ファイルをtoolsやconfigディレクトリなどに置いて、rubocopコマンドはリポイ取りルートなどから実行するようなケースでは、
.rubocop
から始まらない設定ファイルにするのをおすすめします。どういう時に問題になりやすいか
RuboCopがデフォルトで読み込むのは
.rubocop.yml
なので、この名前で設定ファイルを書かれている方が多いかと思うのですが、
.rubocop.yml
がリポジトリルート(またはrubocopコマンドを実行するディレクトリ)においてあるのであれば、問題になりません。ですが、設定ファイルを
config
ディレクトリやtools/rubocop
等においてる場合に問題になりやすいです。. ├── src │ ├── admin_server │ │ └── test │ │ └── bar.rb │ └── api_server │ └── test │ └── foo.rb └── tool └── rubocop └── config └── rubocopの設定ファイル上記のような構成にし、rubocopコマンドをリポジトリルートなどで実行する場合は、
設定ファイルは.rubocop
で始めないほうが楽です。rubocopの設定ファイルFoo/BarCop: Exclude: - '**/test/**/*.rb'testディレクトリではまるっと無視したいcopが時折あると思うのですが、その場合上記のように書けると楽です。
ですが、.rubocop
で始まる設定ファイルの場合、
../../../src/**/test/**/*.rb
というように適切に../
で階層を戻る必要が出てしまいます。
rubocop
で始まらない設定ファイルの場合は、
実行ディレクトリからの相対パスなので'**/test/**/*.rb'
と簡単にかけます。.rubocop.ymlをルートに置かない理由
Inculude/Exclude
の相対パスがややこしい問題は、.rubocop.yml
をリポジトリルートに置けば解決なので通常はそれで全く問題ないと思っています。ただ、さまざまな都合でtoolsやconfigディレクトリなどの下にRuboCopの設定ファイルを置きたいケースがあると思っています。
- 一つのリポジトリの中に複数のアプリがあり、RuboCopの設定は共通にしたい
- 上の方で例に上げたように、
api_server
,admin_server
のようケース- メインのアプリは一つだが、デプロイ・コード生成・バッチなどのような、メインアプリとは別の小さなアプリ・スクリプトでディレクトリを切っているケース
- rubocop gemをアプリのGemfileに入れたくない・入れにくい
- アプリの実行に必要なgemではない
- 上の方で例に上げたように、
api_server
,admin_server
のようにアプリが複数あってそれぞれのGemfileに入れるのは微妙感ある
- かといって、RuboCopのためだけにリポジトリルートに別途Gemfileを用意するのも微妙
- rubocopをdockerで実行できるようにしておきたい
- Gemfileにrubocopを入れない場合、各ユーザーが
gem install rubocop
するのも微妙なのでdockerで起動できるようにしておきたい- => dockerfileや便利スクリプトの置き場が必要
- RuboCopに関するREADMEや設定のガイドラインを用意したい
- RuboCopの実行の仕方・エディタで動かす方法などを書きたい
- RuboCopの設定は揉めやすいのでガイドラインとしてドキュメント化しておきたい
- => 設定ファイル・ドキュメントを一つのディレクトリにまとめたい
さまざまな都合が存在するかなと思います。
チームや状況によってもちろん異なると思いますが、.rubocop.yml
をリポジトリルートに置かないケースはそれなりにあるのかなと思います。まとめ
inherit_from
に書く相対パス
inherit_from
が書かれた設定ファイルからの相対パス
Include/Exclude
に書く相対パス
- それが書かれた設定ファイルが
.rubocop
で始まるファイル名の場合
- その設定ファイルからの相対パス
- 設定ファイル名が
.rubocop
で始まらない場合
rubocop
コマンドを実行したディレクトリからの相対パス- 設定ファイルをリポジトリルートに置かない場合は、
.rubocop
で始まらない設定ファイル名のほうが楽おまけ
https://speakerdeck.com/vividmuimui/anatafalsezhi-ranairubocopfalseshe-ding
今回の話は、社内LTでの内容をリライトしたものです。メイン部分は同じ話ですが、違う設定に関する話もちょっと書いてあるので、よかったら見てください
- 投稿日:2019-04-05T01:50:18+09:00
Ruby on Rails 5 環境構築にDockerを使ってみた。
Qiita初投稿です。
独学でRuby on Railsを勉強しており、今回はDockerを使って環境構築してみました。
もし間違っている部分などありましたら、ご指摘下さい開発環境
MacOS (Mojave 10.14.4)
Service Version Docker 18.09.2 docker-compose 1.23.2 Ruby 2.6.2 Ruby on Rails 5.2.3 PostgreSQL 11.2 1.Docker Desktop for Mac のインストール
https://docs.docker.com/docker-for-mac/install/
インストールにはある程度時間がかかります。
Dockerのオフィシャルサイトはもちろん全て英語表記ですので、Google翻訳を使って読み進めてもいいでしょう。
手順通りに設定していくと、クジラが微笑んでくれます。可愛いですね。2.アプリケーション構築のための各種ファイル設定
開発していくディレクトリにアプリケーションを構築するために必要な4つのファイルを設定します。
今回はUserフォルダ配下にmy_appディレクトリを作成しました。
ファイルがない場合は、コンソール上で$ cd 開発ディレクトリ
→$ touch Gemfile.lock
で対応してください。
- Dockerfile
- Gemfile
- Gemfile.lock
- docker-compose.yml
DockerfileFROM ruby:2.6.2 RUN apt-get update -qq && apt-get install -y nodejs postgresql-client RUN apt-get install -qy git RUN mkdir /my_app WORKDIR /my_app ADD Gemfile /my_app/Gemfile ADD Gemfile.lock /my_app/Gemfile.lock RUN bundle install COPY . /my_app
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
の行は、&&
を消してRUN apt-get update -qq RUN apt-get install -y nodejs postgresql-clientと記述しても大丈夫です。
Gemfilesource 'https://rubygems.org' gem 'rails', '5.0.0.1'Gemfile.lock(空ファイル)docker-compose.ymlversion: "3" services: db: image: postgres volumes: - ./tmp/db:/var/lib/postgresql/data web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/my_app ports: - "3000:3000" depends_on: - db3.プロジェクト・データベースの作成
データベースをPostgreSQLに指定して、これまで設定したファイルを基にRailsプロジェクトを作成します。
terminal$ docker-compose run web rails new . --force --database=postgresqlRails関連のファイルが作成されました。
データベースの情報を設定するために、config/database.ymlを変更し、データベースを作成していきます。config/database.yml(抜粋)default: &default adapter: postgresql encoding: unicode # For details on connection pooling, see Rails configuration guide # http://guides.rubyonrails.org/configuring.html#database-pooling host: db username: postgres password: pool: 5terminal$ docker-compose run web rake db:create
4.Dockerの起動・停止
最後にコンテナをビルドしてから、Dockerを起動させましょう。
terminal$ docker-compose build $ docker-compose uphttp://localhost:3000 にアクセスして、Railsプロジェクトが立ち上がっているか確認してみましょう。
この画面が表示されていれば環境構築完了です。お疲れさまでした!
Dockerを停止する際は以下をコマンドに入力してください。
terminal$ docker-compose stop
(参考リンク)
Docker オフィシャルページ
Qiita記事
- 投稿日:2019-04-05T00:41:57+09:00
RailsでDBに絵文字を保存したい
特に設定しなければ絵文字を書いて保存しようとすると、
Incorrect string value
のエラーが発生します。絵文字を保存する場合は、
config/database.yml
のencodingをutfmb4
にします。default: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: socket: /tmp/mysql.sockまた、テーブルを作る際も
CHARSET=utfmb4
を設定します。create_table "test_table", options: "CHARSET=utf8mb4" do |t| t.string "name", null: false endすでに、DBを作っている場合は一度DBをdropして、DBから作り直す必要があります。