ESM アジャイル事業部 開発者ブログ

永和システムマネジメント アジャイル事業部の開発者ブログです。

浮動小数点数のバイナリ表現を10進数表記へ変換してみる

こんにちは、星野源です。すみません取り乱しました、はたけやまです。

最近は趣味でCPUを自作しています。自作のCPUを浮動小数点演算に対応させるためにオープンソースのFPUコア(浮動小数点演算装置)の使い方を調べていたのですが、FPUとやりとりするデータが浮動小数点数のビット列なので、返ってきた結果が正しいのか正しくないのかがパッと見で分からなくて生きるのが辛い...( 浮動小数点数の 0x3F800000 が10進数表記でいくつか分かります?答えは 1.0です)

そこで、浮動小数点数のビット列を10進数表記へ変換するスクリプトを書いてみました。

f:id:htkymtks:20210520234418p:plain (↑ FPUとのデータのやりとり)

浮動小数点数の構造

変換スクリプトを書く前に浮動小数点数について軽く説明を。「浮動小数点数」は小数点を含む数値をビット列にエンコードする表現方式で、現在では「IEEE 754」として標準化されたものが広く使われています。IEEE 754で定義されている浮動小数点数には32ビットの単精度浮動小数点数(single)や64ビットの倍精度浮動小数点数(double)などがあります。

32ビットの単精度浮動小数点数は以下のような内部構造を持ちます。

  • 符号部(sign)1ビット
  • 指数部(exponent)8ビット
  • 仮数部(fraction)23ビット

f:id:htkymtks:20210520235020p:plain ( File:Float example.svg - Wikimedia Commons )

手作業で浮動小数点数を求める

浮動小数点数への理解を深めるため、10進数の「-118.625」を例にして符号部、指数部、仮数部に入る値を計算してみます。

(1) 符号部

  • 符号部には、正の数の場合は0が、負の数の場合は1が入る
  • -118.625は負の数なので、「符号部 = 1」となる
  • (符号を求めた後は、-118.625 ではなく118.625 で後続の計算を行う)

(2) 10進数を2進数に変換する

  • 118.625を2進数に変換します
  • まずは整数部の118と小数部の0.625に分ける
  • 整数部
    • 118 = (64 * 1) + (32 * 1) + (16 * 1) + (8 * 0) + (4 * 1) + (2 * 1) + (1 * 0)
    • 10進数の「118」を2進数にすると「1110110」となる
    • 整数部の2進数表記 = 1110110
  • 小数部
    • 0.625 = (0.5 * 1) + (0.25 * 0) + (0.125 * 1)
    • 10進数の「0.625」を2進数にすると「0.101」となる
    • 小数部の2進数表記 = 0.101
  • 整数部 + 小数部
    • 1110110 + 0.101 = 1110110.101
  • 118.625の2進数表記 = 1110110.101

(3) 求めた2進数を指数表記へ変換

  • 1110110.101 = 1.110110101 * (2 ^ 6)
  • 「仮数 = 1.110110101」「指数 = 6」となる

(4) 指数部

(5) 仮数部

  • (3) で求めた仮数1.110110101のうち、仮数の先頭は常に「1.」となるので省略し、仮数部23ビットに足りない分はうしろに0を詰めて、「仮数部 = 11011010100000000000000」となる

(6) 符号 + 指数部 + 仮数部

  • 符号「1」+ 指数部「10000101」+ 仮数部「11011010100000000000000」
  • 「-118.625(10進数)」の32ビット単精度浮動小数点数の表記は「11000010111011010100000000000000」となる

以下のサイトを使って、計算した浮動小数点数の答え合わせしてみましょう。

手作業で求めた計算結果が「11000010111011010100000000000000」で、シミュレータで計算した結果が「11000010111011010100000000000000」。どうやら合ってそうです。

「浮動小数点数のバイナリ表現→10進数」変換スクリプト

32ビットの単精度浮動小数点数への理解が深まったので、さっそくスクリプトを書いてみます。

# bin2float.rb

# 32ビット単精度浮動小数点数の正式名称は「binary32」
class Binary32
  def initialize(bin32_str)
    # セパレータ `_` を取り除く
    str = bin32_str.gsub(/_/, '')

    if str.size == 8
      # 16進数表記の場合は2進数表記へ変換
      str = "%032b" % str.to_i(16)
    end

    raise 'Invalid Binary32 string' unless str.size == 32
    
    # 0バイト目は符号(+ or -)
    @sign = str[0]
    # 1〜8バイト目は指数部
    @exp = Exponent8.new(str[1..8])
    # 9〜32バイト目は仮数部
    @fract = Fraction23.new(str[9..])
  end

  def to_f
    # ゼロ
    return 0 if @exp.to_i == 0 && @fract.to_f == 0
    # 非正規数
    return Float::MIN if @exp.to_i == 0 && @fract.to_f != 0
    # 無限大
    return Float::INFINITY if @exp.to_i == 255 && @fract.to_f == 0
    # NaN
    return Float::NAN if @exp.to_i == 255 && @fract.to_f != 0

    # 符号が0ならプラスの値、1ならマイナス
    sign = @sign == "0" ? 1 : -1
    # 仮数部を求める際に省略した1.0を戻す
    fract = @fract.to_f + 1
    # 指数部を求める際に足したバイアス(127)を引く
    exp = @exp.to_i - 127
    
    sign * fract * (2 ** exp)
  end
end

# 指数部の8bit
class Exponent8
  def initialize(exp_str)
    @exp = exp_str
  end

  def to_i
    @exp.to_i(2)
  end
end

# 仮数部の23bit
class Fraction23
  def initialize(fract_str)
    @fract = fract_str
  end

  def to_f
    # (b0 * 0.5) + (b1 * 0.25) + (b2 * 0.125) + (b3 * 0.0625) + ...
    @fract.split(//).map(&:to_i).zip(1..23).map {|b, n|
      b * (2 ** (n * -1)).to_f
    }.sum
  end
end

p Binary32.new(ARGV[0]).to_f

使い方はこんな感じ。先ほど手で計算した「-118.625」も正しく計算できてそうです。

# 正の数
$ ruby bin2float.rb 01000010111011010100000000000000
118.625

# 負の数
$ ruby bin2float.rb 11000010111011010100000000000000  
-118.625

# ゼロ
$ ruby bin2float.rb 00000000000000000000000000000000
0

# 非正規化数(0ではないけれどBinary32で表現できないぐらい小さい値)
$ ruby bin2float.rb 00000000010000000000000000000000
2.2250738585072014e-308(Float::MINが返る)

# 無限大
$ ruby bin2float.rb 01111111100000000000000000000000
Infinity

# NaN
$ ruby bin2float.rb 01111111100000000000000000000001
NaN

# アンダーバーで区切ってもOK
$ ruby bin2float.rb 0_10000101_11011010100000000000000
118.625

# 16進数表記もOK
$ ruby bin2float.rb 42ED4000
118.625

突然のワンライナー

「わーい、できたー」と今回書いたスクリプトをTwitterへ投稿したところ id:udzura によりワンライナーに!!!

packとunpackに向き合ってみた

Rubyでバイナリアンを目指すためにpackとunpackに向き合った結果、僕にもワンライナーできたよー!!!ビッグエンディアンな単精度浮動小数点数としてunpackする必要があるのが罠でした。

gist1416977adf7d370eced64d22e0c273d2

参考

RuboCop オリジナル Cop 活用のオススメ

こんにちは、夜な夜なフォーチュンタワーに登っている nsgc です。

複数人でプロダクトやサービスを作る際に可読性や保守性を向上させるため、 コーディング規約を用意してフォーマットを統一したり、あきらかに不要な記述を静的解析ツールでチェックすることはよくあるかと思います。

Ruby でプログラミングしている場合、そういった Formatter/Linter としては RuboCop が有名ですが、 RuboCop 標準で用意されているルールだけではもの足りず、プロジェクトのコンテキストに特化した独自ルールが欲しい時がありませんか?

そんな時には、オリジナルの Cop の作成をオススメしたい!

tapp をチェックする Cop を作ってみよう

突然ですが tapp というツールをご存知でしょうか? tapp はメソッドチェーンの途中でもオブジェクトを表示できる優れものなのですが、プロダクトコードには入れたくありません。

コードレビューの時に「デバッグ用途の tapp が残ってますよ」 という指摘をしますが、本来はその前に CI で検知してほしいものです。そこで、今回 "tapp が呼ばれているか検知する" 独自 Cop を用意してみましょう。

まずは、Cop クラスの作成です。 RuboCop::Cop::Base を継承したクラスにコールバックを定義し、その中で問題になる条件がないかチェックし、最終的には警告メッセージを出すためのメソッドを呼びだします。

下記例では、メソッド呼び出し時に呼ばれる on_send コールバックを定義し、警告表示のための add_offense を呼んでいます。 RESTRICT_ON_SEND 定数に指定することで on_send で呼ばれる対象を tapp のみに限定し、add_offense で表示するメッセージを MSG 定数で設定しています。

# frozen_string_literal: true

module RuboCop
  module Cop
    module InternalAffairs
      #
      # # bad (ok during development)
      # # using tapp
      # def some_method
      #  do_something.tapp
      # end
      #
      # # good
      # def some_method
      #  do_something
      # end
      #
      class Tapp < Base
        RESTRICT_ON_SEND = %i(tapp).freeze
        MSG = 'Remove debugger'.freeze

        def on_send(node)
          add_offense(node)
        end
      end
    end
  end
end

Cop クラスが用意できたら、次にそのクラスを .rubocop.yml で読み込み、他の Cop 同様に有効にすると使えます。

require:
  - ./lib/rubocop/cop/internal_affairs/tapp

InternalAffairs/Tapp:
  Enabled: true

ここまで読んでどうでしょう?単純なメソッドコールの有り無し位ならとても簡単に作れそうですね。

もう少しリッチな構文チェックをしたい場合やオートコレクトを用意したい場合は公式で用意されている記事 が丁寧に書かれていますし、既存の Cop と近い Cop を作りたい場合は既にある実装 がリファレンスとして参考になります。

さいごに

私が参加しているプロジェクトでは、 特定のロールの利用者が操作時の証跡をデータベース上に残す必要があり、 controller でその証跡レコーディング用のメソッドが呼ばれているかを Cop でチェックしています。

また、Time::DATE_FORMATS で独自のフォーマットを用意しているのですが、それを用いないで書かれた場合にアラートをあげる Cop もいます。

チームのコードレビューで機械的に検出できるものを何度も指摘をしているなら、適応する Cop がないか探してみたり、なければ、自分たちで Cop を用意してチェックを任せ、人間にしかできないレビューに注力していきましょう!

価値創造契約10周年

アジャイル事業部で価値創造契約を担当している平田です。

価値創造契約という新しいサービスを発表して、また、最初のソフトウェアリリースから今年で10年になります。 価値創造契約についてはこちらから。(現在は新規受付は停止しています) https://agile.esm.co.jp/services/value_creating_contract/

詳細は上記のページに書いていますが、特徴を説明すると、初期費用0円とした上で、利用している期間だけ月額利用料をいただき、解約は自由というサービスモデルです。 これを発表した当時は、まだ今ほどアジャイル開発が受け入れられていなかった時期でもあり、「任せてくれたら大丈夫」という自信と覚悟を見せるためのご提案でした。 (そう思うと、請負契約ではなく、リスクをお客様側が持つ形での契約が一般的になっている現在は、隔世の感がありますね。)

価値創造契約はビジネスとして拡大はしなかったものの、ご契約いただいているシステムはいずれも長生きしており、継続的にメンテナンスをしながら使い続けていただいています。 一番長いものはリリースから10年になり、未だにリプレースすることもなく、運用を続けています。

ビジネスとして拡大しなかったのは、いくつも要因が重なったことが理由ですが、その理由のひとつに、我々が継続的に「保守」をやっていく体制をうまく作れなかったというものがあります。 通常、我々が取り組んでいるアジャイルなやり方では、ソフトウェアが動き続ける間、開発チームが継続的に追加開発や不具合修正を続けていきます。 一方、この価値創造契約のモデルでは、リリース後にメンテナンスは行うものの、開発チームを維持し続けるだけの費用をいただいていないため、チームは解散します。チームが解散してしまうことにより、知識の貯蔵庫が失われてしまうのが問題なのではないかと仮説を立てています。

今のところ、価値創造契約というサービスそのものを拡大していく予定はありませんが、上記のような課題を踏まえて、これまで通りの準委任契約での開発に引き続き取り組んでいくことはもちろんのこと、さらにその先をいくような契約形態やサービスを試行していきたいです。

ActiveRecord::LogSubscriber を使って追加でログを出力する

どうも muryoimpl です。

先日 ActiveRecord::LobSubscriber を使ってログ出力に手を加えたので、その意図と実装例をご紹介したいと思います。

ActiveRecord::LogSubscriber とは

ActiveRecord で発行されたクエリをログに出力する役割を担ったクラスです。ログレベルを debug とした場合に、ActiveRecord のクエリを発行した際に実行時間や SQL 文が出力されますが、それはこのクラスが活躍しているおかげです。

ActiveSupport::Notifications の仕組みを使ってログに記録する ActiveSupport::LogSubscriber を継承したクラスとなっていて、ActiveSupport::LogSubscriber の API ドキュメントをみると、ActiveRecord::LogSubscriber を使った例が記載されています。

ActiveRecord::LogSubscriber を継承した独自のクラスを作り、sql メソッドを上書き実装することで、既存のクエリのログ出力に加え、独自のログ出力を追加することができます。

なぜ ActiveRecord::LogSubscriber を使ったのか

今回私は、特定のテーブルのクエリが発行されているかどうか、発行されたときにどのくらいの時間がかかっているのかを確認したいがために、ActiveRecord::LogSubscriber を継承した独自クラスを作成しました。

Rails アプリケーションのログレベル全体を下げるとログの量が爆発的に増えてしまい、大量のSQLにより欲しい情報が埋もれてしまって探しにくくなってしまう問題があり、条件を指定してログ出力できないか?ということで、ActiveRecord::LogSubscriber の出番となったわけです。

この後記載する実装例でも出てきますが、SQL文やクエリに渡されたパラメータが payload として渡されてくるので、これらを条件にして出力する/しないの切り替えを柔軟に設定ができるのではないかと思います。

ActiveRecord::LogSubscriber 自体には、クエリが実装されているソースがどこかを出力するメソッドも実装されているので、あるテーブルへのクエリがどこから出力されるかを調べることもできるでしょう。

テーブルの特定の属性が更新された場合のみSQL文を出力する実装例

以下の環境で動作確認をしています。

  • Ruby on Rails: 6.1.3.1
  • Ruby: 2.7.3

ActiveRecord::LogSubscriber#sql の元の実装 や、ActiveRecord::LogSubscriber の API ドキュメント を参考にして実装しています。

今回は Profile モデルの memo 属性が更新された場合に、ログレベル info で SQL を出力するようにしています。ファイルは config/initializers 以下に配置しています。

class ProfileLogSubscriber < ActiveRecord::LogSubscriber
  def sql(event)
    self.class.runtime += event.duration
    payload = event.payload

    # debug レベルでログが二重に出力されないようにしている。
    # また、自分で実装したクエリ以外のSQLが出力対象とならないようにしている。
    return if logger.debug? || IGNORE_PAYLOAD_NAMES.include?(payload[:name])

    sql_str = payload[:sql]
    # profiles と memo が両方含まれているクエリのみを対象とする
    return if !(/profiles/i.match?(sql_str)) || !(/memo/i.match?(sql_str))

    name  = "#{payload[:name]} (#{event.duration.round(1)}ms)"
    name  = "CACHE #{name}" if payload[:cached]

    binds = type_casted_binds(payload[:type_casted_binds])
    # 親クラスの実装は debug で出力されるようになっているが
    # 今回は info で出力する
    info("#{name}  #{color(sql_str, sql_color(sql_str), true)}; #{binds}")
  end
end

# AcitveSupport::Notifications の"active_record" の namespace に
# ProfileLogSubscriber#sql を登録する
ProfileLogSubscriber.attach_to :active_record

event.payload は Hash で、実行されたSQL文(:sql)、SQLの種類を示す名前(:name)、SQL に bind する値(:bind_casted_binds) 等の情報が含まれています。これらを使って、出力するかどうかの判定と、ログに出力する文字列を作成しています。

ちなみに payload{:name] ですが、ActiveRecrod::Relation#explain を呼び出したときは "EXPLAIN"、テーブル情報等スキーマの情報を取得するSQLが発行された場合は "SCHEMA" になります。その他には、transaction を開始/終了した場合は "TRANSACTION"、定義したテーブルのレコードを操作した場合は "<モデル名> <操作名>" (例: Profile Load)になります。

以下は、前述のコードを config/initializers ディレクトリに配置し、development 環境のログレベルを info に設定した上で、bin/rails console を実行してクエリを発行した様子の画像です。profiles テーブルの memo を更新したときのみ SQL が出力されているのがわかると思います。

f:id:muryoimpl:20210426112809p:plain
rails console での実行例

さいごに

ActiveRecord::LogSubscriber を使って、従来のログ出力に加えて、特定のテーブルや属性に更新があったときのみログ出力する例を紹介しました。

特定の条件のときだけログを追加で出力したいといった場合に、Rails 本体のログ出力機能を書き換えることなく使えるため便利です。実行されたSQLの確認やチューニング、呼び出し元を特定する場面等で使えるので、こういう機能があったな、と覚えておくと活用できる場面に出会うかもしれません。

失敗するということはどのようなことか

突然ですが。 みなさんはソフトウェア開発でどれくらい失敗をしていますか?

失敗することについて、私見をまじえつつ考察してみたいと思います。

プロジェクトで失敗していますか?

仕事でソフトウェア開発をする場合、プロジェクトという単位で開発をすることが多いと思います。 プロジェクトは、その性質から基本的に一点物、その開発は常に初めて経験するものです。

初めてですから、成功も失敗もすべてを予想できるわけではありません。 解決したい領域が自分の不慣れなものであれば、どうすれば成功するのか予測することはいっそう困難なものになります。

これでは何かしら失敗をすることは約束されたようなものですから、心置きなく失敗してもよさそうなものです。

そもそも失敗とは?

しっぱい【失敗】

やりそこなうこと。 目的を果たせないこと。 予期した効果をあげられないこと。

三省堂 スーパー大辞林

プロジェクトで実現したかったことが、開発したソフトウェアで実現できなかったとき、失敗と言うことができそうです。 実現したかったことが実現できなかったのですから、これは本来はネガティブな結果です。

一方でよく耳にするように、失敗することでうまくいかない方法を見つけることができたのだから無駄ではない、という考え方があります。 この立場に立つと、うまくいかいないソフトウェアを開発するためにかけた資源をどう評価するかという問題は残るものの、失敗そのものは無駄だったというわけではありません。

はやく成功に到達するために、はやく失敗するというのは、変化の早い環境の中ではむしろ当然のものという理解も広まってきています。

失敗は、無理に取り除くのではなく、成功のための踏み台ととらえるのがよさそうです。

ただし、それとは別で区別したいものに、避けられたはずの誤りというものもあります。 思い違い、思い込み、確認不足によるミス。 これらはプロジェクトの性質によるものでなく、むしろプロジェクトへの取り組み方によるものでしょう。

失敗することとは別に、誤りをおかさない工夫はしておきたいものです。

いつ失敗するか?

失敗は避けるものでないとして。 では、失敗するならいつがよいのでしょうか?

これは先ほど棚上げした、失敗したことにかけた資源をどう評価するか、という話にもつながりそうです。

最初に考えられるのが、失敗が許される枠を用意して、その中で失敗をすること。 本番に影響を与えず失敗できるように、いわば練習期間を用意して、うまく行く方法やうまくいかない方法を見つけることに当てる。

わたし個人としては、これには異論もあるとは思いますが、自分の能力が資源であり能力を伸ばし効果的に発揮するという点で、プログラマはアスリートと変わらない部分があると考えています。

練習せずに本番にのぞむアスリートがいないように、プログラマにも結果の成否にとらわれない枠があってよいのではないか、と感じています。 個人が自分の能力を伸ばすことに努めるのはもちろんのこと、プロジェクトとしてもチームとしての能力を伸ばすための時間があってよいのかもしれません。

一方で。 できあがった時点では成功なのか失敗なのか、判断がつかないものもあります。 例えば、これはわたし自身が最近経験したことですが、よいユーザインタフェースに改善したはずが、逆にユーザに戸惑いを与え不必要な操作を増やしてしまう結果になってしまったものがありました。 実際にサービスを提供してみないと、何が正解なのか本当にはわからないものです。

ユーザに損害がおよぶ事態はさけなければなりませんが、時には実世界で成否をはかることも必要で、プロジェクトはそんな状況も加味しておくべきなのかも知れません。

うまく失敗できているか?

失敗とは何で、いつなら失敗できるか考えてみましたが、もう一つ他にも重要な要素がありそうです。 失敗することの得手不得手です。

正直なところ。 わたしは、失敗することが下手です。 失敗すれば凹むし、めげるし、ふさぎます。 不必要なほどダメージを受けます。 ダメージを受けるというよりも、自分自身にダメージを与えているというのが正解かもしれません。

失敗できる条件がプロジェクトに用意されているとしても、自分の気持ちとして失敗をゆるせるかはまた別の話。

感情は、本人以外が直接働きかけることはできません。 ですがそれでも、関わり方を工夫することはできるかも知れません。

以前、ソフトウェア開発を委託する立場だった時期がありました。 そのとき心がけていたこととして、報告される失敗に対して深刻な印象を与えないようにする、というものがあります。

深刻になり失敗してしまったことに心がとらわれていると、課題の解決のための力が削がれてしまいます。 当時はそこまで意識していたわけではないのですが、解決のために深刻にならないように、しかし真剣に真摯に取り組めるように、感情の余計な負担を減らしたかったのだと思います。

失敗したらどうするか?

共有しましょう。

なぜそうなっているのか。 その背景を知らないでいると、不用意に「改善」してしまい、失敗によって得られていた知見を失うという誤りをおかす危険があります。 その結果、その知見によって防がれていた課題が再び現れ、重大な事故につながることもあります。

失敗することが、うまくいかないことを知るための行為であるなら、その知識を活かさない手はありません。

ここでもまた「失敗を共有したらあげつらわれる」ことがあると、心理的な負担や障壁になってしまいます。 そうならないためにも、心理的に失敗できる環境が重要なのだと思います。

失敗のために

高い頂には広い裾野が必要です。 そして失敗は、成功のための裾野だと思います。

失敗だけが裾野ではありませんが、失敗もまた裾野を広げる役に立っているように思います。

過ちをおかすのではなく、裾野を広げるために、積極的に失敗することができたらと思います。

Rails / OSS パッチ会オンライン 2021年4月のお知らせ

2021年4月の Rails / OSS パッチ会を 4月22日(木)にオンライン開催します。

この会をひとことでいうと、日頃のお仕事で使っている Rails をはじめとする OSS への upstream にパッチを送る会です。

会には Ruby と Rails のコミッターである顧問の a_matsuda もいますので、例えば Rails に送るパッチのネタがあるけれど、パッチを送るに適しているかの判断やパッチを送る流れが悩ましいときなど a_matsuda に相談して足がかりにするなどできます。

開催時間は 17:00-19:00 となりますがご都合のあう方はぜひご参加下さい。Zoom あたりのテレビ会議システムを使います。

当日の招待 URL は Idobata の esminc/rails ルームで共有する予定です。

idobata.io

特に募集ページなど設けませんが、上記理由から Idobata のアカウントが必要になると思います。

以下、前回の活動が関わる成果です。

koic: activerecord-oracle_enhanced-adapter

github.com

Oracle enhanced adapter の CI が master ブランチで落ちていたところ、私の手元で進めていた部分までを push して、続きを yahonda さんが kamipo さんとディスカッションしつつ、問題の解決まで進めてくれました。

osyo-manga: RuboCop

github.com

RuboCop でマルチバイト圏のソースコードを扱う場合の警告へのハイライト範囲の修正をされているパッチで、osyo-manga さんがパッチ会に問題点を持ってきてくれ、後日 PR を開いてくれました。このパッチへのエピソードについて以下の記事にも記しています。

koic.hatenablog.com


開催中の RailsConf や、最近の Ruby / Rails まわりの動向に関する話題などあるかもしれません。

その他の開催方針については以下の Gist に記していますので、ご参照ください。

Reboot Rails/OSS meetup online · GitHub

開発環境と金の弾丸

昨今の IT エンジニア募集企業では『開発 PC を選べる』というのが一つの潮流にある気がしています。永和システムマネジメント (以下 ESM) でも自分のパフォーマンスが最も出せる PC を選ぶといった制度が古くからあり、今回はそれにまつわるストーリーを書いてみます。

  • Apple の法人購入するときには、キーボードの選択に気をつけよう
  • 法人契約で間違えて購入したとき
  • 16コア、64GBは正義。コアとメモリは積めるだけ積め

以上のショートショート3本立てです。

Apple の法人購入で気をつけること

ESM では定期的に買い替えのタイミングを設けており、これはそんな MacBook Pro を買い換えるときのとある日の出来事です。

ESM で MacBook Pro を購入する際には Apple で法人契約購入するのですが、先日弊社メンバーで立て続けに起きた、ちょっとした注意点を共有しておきます。

舞台は以下のツイートです。

数日後、同僚にもまさかの同じことが起きました。

言い訳じみた原因としては、法人での購入画面への注意力散漫といったものだったと思われます。

MacBook Pro の構成をカスタマイズするときに、デフォルトで『日本語 (JIS) 』キーボードが選択されていたので、US 配列が良い場合は『英語 (米国) 』を選択し直す必要があります。

(注: 以下は US 配列キーボード「選択後」の画面です)

f:id:koic:20210402095901p:plain

CPU やメモリのカスタマイズと異なり、購入金額が変わるわけではないので、会社の予算に気を取られて見落とさないよう気をつけると良いです。

Apple の法人購入で間違えて購入した場合

一定期間の間であれば、交換してもらえます。私も諦めかけていたところ、バックオフィスのメンバーから交換の流れを教えてもらって US キーボードへの転身をとげました。 とりわけ Apple 社に迷惑を掛けてしまったあたり反省しかないのですが、おかげで満足なキーボードで生活できています。

あきらめる前に、お勤めの会社で交換可能か確認をとってみましょう。

16コア、64GBは正義。コアとメモリは積めるだけ積め

「買い替えてどうなったの?」というところで、OSS での開発を例に挙げると、RuboCop のフルビルドにかかる所要時間がおおよそ半分程度になりました。

RuboCop では test-queue を使ったテストの並列化をしているため、コア/ハイパースレッディングとメモリが増えればそれだけビルドの待ち時間が減るという公式です。

github.com

アクティビティモニタでみるとこんな感じで、すべてのコアが働いていることが確認できます。

f:id:koic:20210402095836p:plain

PC スペックとフルビルドの Before / After を並べてみます。

⏳ Before (1:54.53)

  • プロセッサ: 2.7 GHz クアッドコアIntel Core i7
  • メモリ: 16 GB 2133 MHz LPDDR3
% time bundle exec rake
Files:         567
Modules:        88 (   13 undocumented)
Classes:       529 (    2 undocumented)
Constants:     882 (  869 undocumented)
Attributes:     31 (    0 undocumented)
Methods:       990 (  867 undocumented)
 30.52% documented
Starting test-queue master (/tmp/test_queue_1738_17060.sock)

==> Summary (8 workers in 42.8440s)

    [ 1]                         1960 examples, 0 failures, 4 pending        80 suites in 24.9043s      (pid 1774 exit 0 )
    [ 2]                         2626 examples, 0 failures, 6 pending        87 suites in 24.9056s      (pid 1775 exit 0 )
    [ 3]                                     117 examples, 0 failures         1 suites in 42.8359s      (pid 1776 exit 0 )
    [ 4]                                    2309 examples, 0 failures        81 suites in 42.8358s      (pid 1777 exit 0 )
    [ 5]                                    2996 examples, 0 failures        87 suites in 42.8372s      (pid 1778 exit 0 )
    [ 6]                         1436 examples, 0 failures, 1 pending        78 suites in 42.8379s      (pid 1779 exit 0 )
    [ 7]                                     637 examples, 0 failures        53 suites in 42.8379s      (pid 1780 exit 0 )
    [ 8]                                    3490 examples, 0 failures        85 suites in 42.8312s      (pid 1781 exit 0 )

Starting test-queue master (/tmp/test_queue_1738_18680.sock)

==> Summary (8 workers in 41.1013s)

    [ 1]                                     117 examples, 0 failures         1 suites in 41.0932s      (pid 1860 exit 0 )
    [ 2]                                    1518 examples, 0 failures        78 suites in 41.0933s      (pid 1861 exit 0 )
    [ 3]                                    2480 examples, 0 failures        86 suites in 41.0947s      (pid 1862 exit 0 )
    [ 4]                                    2259 examples, 0 failures        86 suites in 41.0952s      (pid 1863 exit 0 )
    [ 5]                         2786 examples, 0 failures, 5 pending        87 suites in 41.0958s      (pid 1864 exit 0 )
    [ 6]                                    2066 examples, 0 failures        79 suites in 41.0960s      (pid 1865 exit 0 )
    [ 7]                                     591 examples, 0 failures        46 suites in 41.0957s      (pid 1866 exit 0 )
    [ 8]                         3754 examples, 0 failures, 6 pending        89 suites in 41.0892s      (pid 1867 exit 0 )

Running RuboCop...
Inspecting 1261 files
.............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

1261 files inspected, no offenses detected
bundle exec rake  515.00s user 34.02s system 479% cpu 1:54.53 total

⏳ After (1:11.55)

  • プロセッサ: 2.4 GHz 8コアIntel Core i9
  • メモリ: 64 GB 2667 MHz DDR4
% time bundle exec rake
Files:         567
Modules:        88 (   13 undocumented)
Classes:       529 (    2 undocumented)
Constants:     882 (  869 undocumented)
Attributes:     31 (    0 undocumented)
Methods:       990 (  867 undocumented)
 30.52% documented
Starting test-queue master (/tmp/test_queue_27867_17060.sock)

==> Summary (16 workers in 26.9534s)

    [ 1]                                     111 examples, 0 failures         1 suites in 12.8340s      (pid 27900 exit 0 )
    [ 2]                                    1527 examples, 0 failures        43 suites in 12.8344s      (pid 27901 exit 0 )
    [ 3]                                     117 examples, 0 failures         1 suites in 26.9430s      (pid 27902 exit 0 )
    [ 4]                         1029 examples, 0 failures, 4 pending        47 suites in 26.9427s      (pid 27903 exit 0 )
    [ 5]                                    1529 examples, 0 failures        51 suites in 26.9428s      (pid 27904 exit 0 )
    [ 6]                                    1699 examples, 0 failures        48 suites in 26.9439s      (pid 27905 exit 0 )
    [ 7]                                     959 examples, 0 failures        46 suites in 26.9438s      (pid 27906 exit 0 )
    [ 8]                                      68 examples, 0 failures         1 suites in 26.9437s      (pid 27907 exit 0 )
    [ 9]                                    1470 examples, 0 failures        50 suites in 26.9435s      (pid 27908 exit 0 )
    [10]                                     269 examples, 0 failures        21 suites in 26.9434s      (pid 27909 exit 0 )
    [11]                                      34 examples, 0 failures         1 suites in 26.9432s      (pid 27910 exit 0 )
    [12]                                     920 examples, 0 failures        45 suites in 26.9430s      (pid 27911 exit 0 )
    [13]                                    1473 examples, 0 failures        49 suites in 26.9428s      (pid 27912 exit 0 )
    [14]                         1171 examples, 0 failures, 1 pending        50 suites in 26.9431s      (pid 27913 exit 0 )
    [15]                         1630 examples, 0 failures, 1 pending        49 suites in 26.9428s      (pid 27914 exit 0 )
    [16]                         1565 examples, 0 failures, 5 pending        49 suites in 26.9414s      (pid 27915 exit 0 )

Starting test-queue master (/tmp/test_queue_27867_18680.sock)


==> Summary (16 workers in 28.2100s)

    [ 1]                                     111 examples, 0 failures         1 suites in 13.5583s      (pid 27954 exit 0 )
    [ 2]                                    1590 examples, 0 failures        48 suites in 13.5583s      (pid 27955 exit 0 )
    [ 3]                                    1316 examples, 0 failures        46 suites in 13.5583s      (pid 27956 exit 0 )
    [ 4]                         1231 examples, 0 failures, 5 pending        50 suites in 13.5582s      (pid 27957 exit 0 )
    [ 5]                                      68 examples, 0 failures         1 suites in 13.5581s      (pid 27958 exit 0 )
    [ 6]                                     256 examples, 0 failures        21 suites in 13.5578s      (pid 27959 exit 0 )
    [ 7]                                     895 examples, 0 failures        47 suites in 13.5578s      (pid 27960 exit 0 )
    [ 8]                                     931 examples, 0 failures        44 suites in 13.5579s      (pid 27961 exit 0 )
    [ 9]                                    1565 examples, 0 failures        48 suites in 13.5578s      (pid 27962 exit 0 )
    [10]                         1595 examples, 0 failures, 4 pending        45 suites in 13.5578s      (pid 27963 exit 0 )
    [11]                                     117 examples, 0 failures         1 suites in 28.2005s      (pid 27964 exit 0 )
    [12]                                      34 examples, 0 failures         1 suites in 28.2004s      (pid 27965 exit 0 )
    [13]                         1153 examples, 0 failures, 1 pending        49 suites in 28.2002s      (pid 27966 exit 0 )
    [14]                                    1596 examples, 0 failures        50 suites in 28.2003s      (pid 27967 exit 0 )
    [15]                                    1613 examples, 0 failures        50 suites in 28.2000s      (pid 27968 exit 0 )
    [16]                         1500 examples, 0 failures, 1 pending        50 suites in 28.2001s      (pid 27969 exit 0 )

Running RuboCop...
Inspecting 1261 files
.............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

1261 files inspected, no offenses detected
bundle exec rake  447.86s user 30.88s system 669% cpu 1:11.55 total

16 コア、64GB の開発環境は正義。金の弾丸が効いたことがわかります。

余談ですが、会社としては (業務がまわれば) Linux や WSL という選択肢もある中、私が MacBook を選んでいる理由は、Rails アプリケーション開発が仕事のベースにあるため、Rails の作者である DHH が選択している MacBook を使うことにしています。

これは『開発 PC を選べる』の根底にある「自分のパフォーマンスが最も出せる PC を選ぶ」への観点として、開発環境の構築にハマらないように、もしハマった時は The Rails Doctrine にある「数は安全性を生む」による解決が働きやすいことへの期待があるためです。

qiita.com

以上、金の弾丸によって開発効率を上げた一例の紹介でした。

もっとコアがあれば開発効率の向上に活かせるのに予算が足りないよ、、、ということであれば組織の予算をハックすることを検討できるかもしれません。PC の必要経費は年々上がっているので、時代背景とあっているか上長と掛け合ってみましょう。人件費に比べれば PC は安いもので、何より開発者にとって快適な開発環境はすこぶる良いものです (健闘を祈る!) 。

最後にいつものです。

agile.esm.co.jp