Quantcast
Channel: Big Sky
Viewing all 121 articles
Browse latest View live

開発速度を加速するツール、goemon を書いた。

$
0
0

SPA (Single Page Application) を書いていると、いちいちブラウザをリロードするのが面倒で、かつ js を minify してページをリロードするといった面倒な手間を出来れば何も設定せずにやりたい(もしくは微量な設定だけでやりたい)、という思いから goemonというツールを書きました。

mattn/goemon - GitHub
https://github.com/mattn/goemon

goemon は、コマンドラインツールとして使います。まず

$ goemon -g > goemon.yml

goemon.ymlを生成します。個人的にカスタマイズしたい人は生成されたファイルを変更して使って下さい。

# Generated by goemon -g
livereload: :35730
tasks:
match: './assets/*.js'
  commands:
  minifyjs -m -i ${GOEMON_TARGET_FILE} > ${GOEMON_TARGET_DIR}/${GOEMON_TARGET_NAME}.min.js
  :livereload /
match: './assets/*.css'
  commands:
  :livereload /
match: './assets/*.html'
  commands:
  :livereload /
match: '*.go'
  commands:
  go build
  :restart
  :livereload /
$ goemon ./foo

fooが起動します。サーバを起動するコマンドを渡す事になります。デフォルトの設定だと以下の様に動作します。

  • assets/*.jsが変更されたら同じディレクトリに minify した js(拡張子: .min.js) を生成し、/を livereload (自動再読み込み)する。
  • assets/*.htmlassets/*.cssが変更されたら /を livereload する。
  • *.goが変更されたら go buildしてアプリを再起動(引数を再実行)した後、/を livereload する。

つまりは開発者はエディタで編集するだけでブラウザ勝手にリロードしてウマーーー!という想定

別に golang だけのツールという訳では無いので、ruby や node/io.js 等でも使えます。ワイルカードパターンには **/*.jsという複数ディレクトリ階層のワイルドカードも使えます。もちろんですが、Windows でも動作します。詳しくは README を参照下さい。

goemon で使える便利なパターンやコマンドがあれば、ぜひ pull-request して下さい。


Protocol Buffers を利用した RPC、gRPC を golang から試してみた。

$
0
0
grpc/grpc · GitHub

gRPC - An RPC library and framework

https://github.com/grpc/grpc

gRPC は Google が開発しているRPC(リモートプロシージャコール)のライブラリとフレームワークで、通信層は HTTP/2 を介して行われます。

データ層については、固定されている訳ではなくあくまでデフォルトで Protocol Buffers が使われる様になっています。使用出来るプログラミング言語は現在、C++, Node.js, Python, Ruby, Objective-C, PHP, C# となっています。

実はこれら以外にも grpc-go という、なぜかこのリストに加えられていないオフィシャルリポジトリがあります。

grpc/grpc-go - GitHub

gRPC-Go The Go implementation of gRPC

https://github.com/grpc/grpc-go

今日はこれを使って、gRPC 通信を試してみたいと思います。

まず proto ファイルを作ります。Protocol Buffers の proto ファイルの作り方については過去に何度かやったのと同じです。

proto ファイルは以下の通り。

customer_service.proto
syntax = "proto3";

package proto;

service CustomerService {
  rpc ListPerson(RequestType) returns (stream Person) {};
  rpc AddPerson(Person) returns (ResponseType) {}

}

message ResponseType {
}

message RequestType {
}

message Person {
  string name = 1;
  int32  age  = 2;
}

Protocol Buffers のバージョンが上がり、service の定義も書ける様になりました。定義内容は Person というデータ構造に対して、一覧に追加する AddPerson、一覧を返す ListPersonを定義しました。

この proto ファイルからスタブを作ります。スタブは protoc というコマンドを使うのですが、grpc-go の場合は protoc にデフォルトで組み込まれていない為、protoc のプラグインとして動作させる必要があります。

まず protoc をダウンロードしてきます。Google のサイトに置いてある物は stable release なので今回の service は使えません。ご注意ください。github 上の alpha リリース物をダウンロードします。

Releases - google/protobuf - GitHub
https://github.com/google/protobuf/releases

ダウンロードした protoc をパスの通った所に置いて下さい。

$ go get github.com/golang/protobuf/protoc-gen-go

これで grpc-go が protoc のプラグインとしてインストール出来ます。

protoc --go_out=plugins=grpc:. customer_service.proto

すると、以下の customer_service.pb.goというファイルが生成されます。

// Code generated by protoc-gen-go.
// source: customer_service.proto
// DO NOT EDIT!

/*
Package proto is a generated protocol buffer package.

It is generated from these files:
    customer_service.proto

It has these top-level messages:
    ResponseType
    RequestType
    Person
*/
package proto

import proto1 "github.com/golang/protobuf/proto"

import (
    context "golang.org/x/net/context"
    grpc "google.golang.org/grpc"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto1.Marshal

type ResponseType struct {
}

func (m *ResponseType) Reset()         { *m = ResponseType{} }
func (m *ResponseType) String() string { return proto1.CompactTextString(m) }
func (*ResponseType) ProtoMessage()    {}

type RequestType struct {
}

func (m *RequestType) Reset()         { *m = RequestType{} }
func (m *RequestType) String() string { return proto1.CompactTextString(m) }
func (*RequestType) ProtoMessage()    {}

type Person struct {
    Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
    Age  int32  `protobuf:"varint,2,opt,name=age" json:"age,omitempty"`
}

func (m *Person) Reset()         { *m = Person{} }
func (m *Person) String() string { return proto1.CompactTextString(m) }
func (*Person) ProtoMessage()    {}

func init() {
}

// Client API for CustomerService service

type CustomerServiceClient interface {
    ListPerson(ctx context.Context, in *RequestType, opts ...grpc.CallOption) (CustomerService_ListPersonClient, error)
    AddPerson(ctx context.Context, in *Person, opts ...grpc.CallOption) (*ResponseType, error)
}

type customerServiceClient struct {
    cc *grpc.ClientConn
}

func NewCustomerServiceClient(cc *grpc.ClientConn) CustomerServiceClient {
    return &customerServiceClient{cc}
}

func (c *customerServiceClient) ListPerson(ctx context.Context, in *RequestType, opts ...grpc.CallOption) (CustomerService_ListPersonClient, error) {
    stream, err := grpc.NewClientStream(ctx, &_CustomerService_serviceDesc.Streams[0], c.cc, "/proto.CustomerService/ListPerson", opts...)
    if err != nil {
        return nil, err
    }
    x := &customerServiceListPersonClient{stream}
    if err := x.ClientStream.SendProto(in); err != nil {
        return nil, err
    }
    if err := x.ClientStream.CloseSend(); err != nil {
        return nil, err
    }
    return x, nil
}

type CustomerService_ListPersonClient interface {
    Recv() (*Person, error)
    grpc.ClientStream
}

type customerServiceListPersonClient struct {
    grpc.ClientStream
}

func (x *customerServiceListPersonClient) Recv() (*Person, error) {
    m := new(Person)
    if err := x.ClientStream.RecvProto(m); err != nil {
        return nil, err
    }
    return m, nil
}

func (c *customerServiceClient) AddPerson(ctx context.Context, in *Person, opts ...grpc.CallOption) (*ResponseType, error) {
    out := new(ResponseType)
    err := grpc.Invoke(ctx, "/proto.CustomerService/AddPerson", in, out, c.cc, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

// Server API for CustomerService service

type CustomerServiceServer interface {
    ListPerson(*RequestType, CustomerService_ListPersonServer) error
    AddPerson(context.Context, *Person) (*ResponseType, error)
}

func RegisterCustomerServiceServer(s *grpc.Server, srv CustomerServiceServer) {
    s.RegisterService(&_CustomerService_serviceDesc, srv)
}

func _CustomerService_ListPerson_Handler(srv interface{}, stream grpc.ServerStream) error {
    m := new(RequestType)
    if err := stream.RecvProto(m); err != nil {
        return err
    }
    return srv.(CustomerServiceServer).ListPerson(m, &customerServiceListPersonServer{stream})
}

type CustomerService_ListPersonServer interface {
    Send(*Person) error
    grpc.ServerStream
}

type customerServiceListPersonServer struct {
    grpc.ServerStream
}

func (x *customerServiceListPersonServer) Send(m *Person) error {
    return x.ServerStream.SendProto(m)
}

func _CustomerService_AddPerson_Handler(srv interface{}, ctx context.Context, buf []byte) (proto1.Message, error) {
    in := new(Person)
    if err := proto1.Unmarshal(buf, in); err != nil {
        return nil, err
    }
    out, err := srv.(CustomerServiceServer).AddPerson(ctx, in)
    if err != nil {
        return nil, err
    }
    return out, nil
}

var _CustomerService_serviceDesc = grpc.ServiceDesc{
    ServiceName: "proto.CustomerService",
    HandlerType: (*CustomerServiceServer)(nil),
    Methods: []grpc.MethodDesc{
        {
            MethodName: "AddPerson",
            Handler:    _CustomerService_AddPerson_Handler,
        },
    },
    Streams: []grpc.StreamDesc{
        {
            StreamName:    "ListPerson",
            Handler:       _CustomerService_ListPerson_Handler,
            ServerStreams: true,
        },
    },
}

あとは golang の開発手順通り、これを参照したサーバとクライアントを作ります。まずサーバ

package main

import (
    "golang.org/x/net/context"
    "log"
    "net"
    "sync"

    "google.golang.org/grpc"

    pb "github.com/mattn/grpc-example/proto"
)

type customerService struct {
    customers []*pb.Person
    m         sync.Mutex
}

func (cs *customerService) ListPerson(p *pb.RequestType, stream pb.CustomerService_ListPersonServer) error {
    cs.m.Lock()
    defer cs.m.Unlock()
    for _, p := range cs.customers {
        if err := stream.Send(p); err != nil {
            return err
        }
    }
    return nil
}

func (cs *customerService) AddPerson(c context.Context, p *pb.Person) (*pb.ResponseType, error) {
    cs.m.Lock()
    defer cs.m.Unlock()
    cs.customers = append(cs.customers, p)
    return new(pb.ResponseType), nil
}

func main() {
    lis, err := net.Listen("tcp"":11111")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    server := grpc.NewServer()

    pb.RegisterCustomerServiceServer(server, new(customerService))
    server.Serve(lis)
}

何の面白味もないフツーに RPC のコードですね。一覧を返す場合は、配列ではなく stream に対して書き込む事で一覧を返す事になります。またどうやら Protocol Buffers では引数の無いインタフェースは作れない様なので、RequestType と ResponseType という型も用意していますが、要らないからといって nil を返したりすると落ちるので要注意です。

次にクライアント

package main

import (
    "fmt"
    "io"
    "strconv"

    pb "github.com/mattn/grpc-example/proto"
    "github.com/mattn/sc"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
)

func add(name string, age interror {
    conn, err := grpc.Dial("127.0.0.1:11111")
    if err != nil {
        return err
    }
    defer conn.Close()
    client := pb.NewCustomerServiceClient(conn)

    person := &pb.Person{
        Name: name,
        Age:  int32(age),
    }
    _, err = client.AddPerson(context.Background(), person)
    return err
}

func list() error {
    conn, err := grpc.Dial("127.0.0.1:11111")
    if err != nil {
        return err
    }
    defer conn.Close()
    client := pb.NewCustomerServiceClient(conn)

    stream, err := client.ListPerson(context.Background(), new(pb.RequestType))
    if err != nil {
        return err
    }
    for {
        person, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }
        fmt.Println(person)
    }
    return nil
}

func main() {
    (&sc.Cmds{
        {
            Name: "list",
            Desc: "list: listing person",
            Run: func(c *sc.C, args []stringerror {
                return list()
            },
        },
        {
            Name: "add",
            Desc: "add [name] [age]: add person",
            Run: func(c *sc.C, args []stringerror {
                if len(args) != 2 {
                    return sc.UsageError
                }
                name := args[0]
                age, err := strconv.Atoi(args[1])
                if err != nil {
                    return err
                }
                return add(name, age)
            },
        },
    }).Run(&sc.C{})
}

最近開発中の、サブコマンドライブラリ sc を使ってみました。サーバが stream から Send で送ってくるのでクライアントもループで回しながら Recv する必要があります。

やったね!

ちゃんと動きました。固い型付けの言語でしっかり動くと気持ちいいですね。今後いろいろなアプリケーションが grpc を使い、HTTP/2 が透過的に使われていく時代になっていくのだと思います。ワクワクしますね。

今日作った物は以下のリポジトリに置いてあります。よろしければ遊んで下さい。

mattn/grpc-example - GitHub
https://github.com/mattn/grpc-example

ruby と C++ と golang で gRPC のベンチマークを取ってみた。

$
0
0

先日、Google が開発しているリモートプロシージャコール、gRPC を golang から使うチュートリアルを書きましたが

Big Sky :: Protocol Buffers を利用した RPC、gRPC を golang から試してみた。
http://mattn.kaoriya.net/software/lang/go/20150227144125.htm

今日は ruby と C++ から触ってみたいと思います。はじめに ruby の方ですが、Ruby 2.2.0 でビルドする事が出来ません。どうしても Ruby 2.2.0 から試したい人は、以下の PR にあるパッチを適用して下さい。

Support ruby 2.2.0 by mattn · Pull Request #894 - grpc/grpc - GitHub
https://github.com/grpc/grpc/pull/894

今回の検証は ruby 2.1.0 で行いました。まず proto ファイルから ruby のスタブを吐くには以下の様に実行します。

$ protoc --ruby_out=. --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_ROOT)/bins/opt/grpc_ruby_plugin customer_service.proto

GRPC_ROOTは grpc を checkout したディレクトリですが、システムにインストールした人は適便書き換えて下さい。golang の時の様にスタブが吐かれるのでサーバであれば

#!/usr/bin/env ruby

require 'grpc'
require 'customer_service_services'

class MyServer < Proto::CustomerService::Service
  def initialize()
    @customers = []
  end
  def add_person(arg, call)
    @customers << arg
    Proto::ResponseType.new
  end
  def list_person(args, call)
    @customers
  end
end

def main
  customers = []
  server = GRPC::RpcServer.new
  server.add_http2_port('0.0.0.0:11111')
  server.handle(MyServer.new)
  server.run
end

main

こんな感じ。クライアントであれば

#!/usr/bin/env ruby

require 'grpc'
require 'customer_service_services'

def main
  stub = Proto::CustomerService::Stub.new('localhost:11111')
  if ARGV.size == 2
    stub.add_person(Proto::Person.new(nameARGV[0], ageARGV[1].to_i))
  else
    stub.list_person(Proto::RequestType.new).each do |x|
      puts "name=#{x.name}, age=#{x.age}"
    end
  end
end

main

こんな感じに実装して下さい。今回は Sync サーバで実装しましたが、Async サーバで実装する場合は customers に排他を行うべきです。

C++ もやり方は変わりません。

protoc --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_ROOT)/bins/opt/grpc_cpp_plugin customer_service.proto

protoc でスタブを吐いてサーバであれば

#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <grpc/grpc.h>
#include <grpc++/server.h>
#include <grpc++/server_builder.h>
#include <grpc++/server_context.h>
#include <grpc++/status.h>
#include <grpc++/stream.h>
#include "customer_service.pb.h"

using namespace proto;
using namespace grpc;

class CustomerServiceImpl final : public CustomerService::Service {
private:
  std::vector<Person> customers;
public:
  Status AddPerson(ServerContext* context, const Person* customer, ResponseType* response) {
    std::cout << "AddPerson" << std::endl;
    customers.push_back(*customer);
    std::cout << "Done" << std::endl;
    return Status::OK;
  }

  Status ListPerson(ServerContext* context, const RequestType* request, ServerWriter<Person>* writer) {
    std::cout << "ListPerson" << std::endl;
    for (const Person& p : customers) {
      writer->Write(p);
    }
    std::cout << "Done" << std::endl;
    return Status::OK;
  }
};

int
main(int argc, char* argv[]) {
  grpc_init();
  std::string server_address("0.0.0.0:11111");
  CustomerServiceImpl service;

  ServerBuilder builder;
  builder.AddPort(server_address);
  builder.RegisterService(&service);
  std::unique_ptr<Server> server(builder.BuildAndStart());
  std::cout << "Server listening on " << server_address << std::endl;
  server->Wait();
  grpc_shutdown();
  return 0;
}
またクライアントであれば
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <grpc/grpc.h>
#include <grpc++/channel_arguments.h>
#include <grpc++/channel_interface.h>
#include <grpc++/client_context.h>
#include <grpc++/create_channel.h>
#include <grpc++/status.h>
#include <grpc++/stream.h>
#include "customer_service.pb.h"

using namespace proto;
using namespace grpc;

int
main(int argc, char** argv) {
  grpc_init();
  std::unique_ptr<CustomerService::Stub> client = CustomerService::NewStub(
      grpc::CreateChannelDeprecated("127.0.0.1:11111", ChannelArguments()));
  ClientContext context;
  RequestType request;
  ResponseType response;
  Person person;

  Status status;
  if (argc == 3) {
    person.set_name(argv[1]);
    person.set_age(atoi(argv[2]));
    status = client->AddPerson(&context, person, &response);
  } else {
    std::unique_ptr<ClientReader<Person>> reader = client->ListPerson(&context, request);
    while (reader->Read(&person)) {
      std::cout << "name=" << person.name() << ", age=" << person.age() << std::endl;
    }
    status = reader->Finish();
  }
  if (!status.IsOk()) {
    std::cout << "ListFeatures rpc failed." << std::endl;
  }
  client.reset();
  grpc_shutdown();
}

この様に実装します。同じ proto ファイルから生成したサーバやクライアントですので、ruby のサーバを起動して、golang のクライアントや C++ のクライアントからリクエストを送る事も出来ますし、C++ をサーバや golang をサーバにしても良いでしょう。

ただ触った感じですが、ruby のサーバは何故かレスポンスが悪かったので調査を兼ねてベンチマークを取ってみました。

シナリオは、ruby、C++、golang それぞれのサーバを起動して C++ のクライアントから要求します。計測は AddPerson の1000回呼び出し、とその1000件入った状態で ListPerson を100回呼び出しを計測しました。

ruby

mattn/grpc-example-rb - GitHub

AddPerson

$ time seq 1000 | xargs -n 1 ./client mattn

real    0m14.287s
user    0m6.667s
sys     0m4.607s

ListPerson

$ time seq 100 | xargs -n 1 ./client > /dev/null

real    0m12.385s
user    0m4.833s
sys     0m4.367s

cpp

mattn/grpc-example-cpp - GitHub

AddPerson

$ time seq 1000 | xargs -n 1 ./client mattn

real    0m12.326s
user    0m6.007s
sys     0m4.710s

ListPerson

$ time seq 100 | xargs -n 1 ./client > /dev/null

real    0m4.304s
user    0m2.430s
sys     0m1.490s

golang

mattn/grpc-example - GitHub

AddPerson

$ time seq 1000 | xargs -n 1 ./client mattn

real    0m13.067s
user    0m6.553s
sys     0m4.737s

ListPerson

$ time seq 100 | xargs -n 1 ./client > /dev/null

real    0m5.755s
user    0m2.767s
sys     0m1.983s

とまぁ、想定通りの結果が出ました。golang が案外頑張ってるなーという印象です。今後時間が出来たら Java や python も触っていく予定です。これまで3言語を同じ proto ファイルからスタブ生成して開発を行ってみましたが、各々扱い方が異なるのでちょっと混乱する可能性があります。自分の好きな言語で実装するのが良いと思います。

サーバ/インフラ徹底攻略 (WEB+DB PRESS plus)サーバ/インフラ徹底攻略 (WEB+DB PRESS plus)
伊藤 直也
技術評論社 / ¥ 2,138 (2014-10-30)
 
発送可能時間:在庫あり。

WEB+DB PRESS Vol.85WEB+DB PRESS Vol.85
菅原 元気
技術評論社 / ¥ 1,598 (2015-02-24)
 
発送可能時間:在庫あり。

Vim 上のファイラを NERDTree から dirvish に乗り換えた。

$
0
0

Vim には netrw というファイラが付属しています。引数にディレクトリを渡すとディレクトリブラウザが開き、HTML のある URL を指定するとダウンロードされた HTML が開き、scp://の様なプロトコルを指定するとそれにあったファイルの開き方をしてくれます。

最近のモダンな Vim 使いの多くは、NERDTreevimfilerを使っているのですが、正直僕は vim からファイルを操作はしない。思考停止せずに目的のファイルを見つけ出したいし、ファイル操作はファイル操作としてシェルからやりたい。なので深い階層のファイルを見つける為の目的として CtrlPを使ってるし、カレントディレクトリのファイル一覧を出すのに NERDTree を使っていた。

でも NERDTree 遅いなー。ツライなー。そんな風に思っていた時にこれを見つけた。

justinmk/vim-dirvish - GitHub

dired on a diet

https://github.com/justinmk/vim-dirvish

filebeagleをベースとしてとことん小さくしたプラグイン。READMEには「Note: there are still some bugs. This plugin isn't ready to use yet.」と書かれているけど僕は乗り換えてしまった。

僕は手癖で

$ vim .

とする癖があるのだけど、その時に出るファイル一覧が netrw も NERDTree もとにかく遅い。思考が停止しそうでイラッとする。でも dirvish だと一瞬で開く。本当にファイル操作したいなら :shで shell やコマンドプロンプトに戻ればいいだけなので僕にはこれが目的にあったプラグインなのです。こういうminimumでenoughなのが僕は好きです。

golang のパッケージが Google Code に依存しているか調べるコマンド作った。

$
0
0
Google Code が終了する様です。
Google Open Source Blog: Bidding farewell to Google Code
http://google-opensource.blogspot.com/2015/03/farewell-to-google-code.html

GitHub が無かった頃、私はコード置き場として Google Code を使っていました。とても安定していたし落ちるのを見た事がありませんでした。

残念ながらその Google Code がサービスを終えようとしています。

しかし我々 Gopher のコードには、この Google Code にホスティングされている golang のパッケージが幾らかあります。

Google Code が終了するまでに、私達は移行を完了させなければなりません。

mattn/check-code-google-com - GitHub
https://github.com/mattn/check-code-google-com

パッケージが code.google.comのパッケージに依存しているかどうかを調べてくれます。

例えば mahoniaに依存している jvgrepであれば

この様に表示され、code.google.comに依存していない twtyであれば

この様に表示されます。中には依存しているパッケージのその依存しているパッケージが code.google.comに依存している事がありますが、それも検索します。-vオプションを付ける事で、どのパッケージが code.google.comに依存しているのかが分かる様になっています。

よろしければどうぞ。

Re: Go で channel が close してるかどうか知りたい というアンチパターン

$
0
0
Goでchannelがcloseしてるかどうか知りたい というアンチパターン - beatsync.net

そもそもGoのchannelがcloseしてるかどうかを知りたいっていう理由は、だいたい「Goのchannelはナイーブだから」というところに起因するのはないかと思います。

https://beatsync.net/main/log20150325.html

golang には元々 closed()という、channel が閉じられているかどうかを返す組み込み関数がありました。しかし廃止されました。

closed は API としては目的を達成出来ているのですが、builtin が1つ増える、channel から取り出す前に一度確認する必要があるという理由で削除されたと私は記憶しています。

Goでchannelがcloseしてるかどうか知りたい というアンチパターン - beatsync.net

selectをつかってブロックする可能性のある文をcaseにかけば一番最初にブロックが外れたやつに分岐します。で、これは@fujiwaraさんに教えてもらった技ですが、なんもしないdefaultを書けばブロックせずにすぐdefaultにおちるとのこと。

https://beatsync.net/main/log20150325.html

実際には closed()が無くなった訳ではなく、型アサーションと同じインタフェースに置き換えられました。

Issue 4243072: code review 4243072: go code: replace closed(c) with x, ok := <-c - Code Review

LGTM http://codereview.appspot.com/4243072/diff/5001/src/pkg/os/inotify/inotify_li... File src/pkg/o...

https://codereview.appspot.com/4243072/
x, ok := <-c

戻り値の2つ目に channel から取り出せたかどうかが bool で返ります。

例: Go Playground

select はどちらかと言うと、複数の channel を同時に待てる仕組みであり、閉じている channel の case には飛ばないという理由で動いています。ですのでどうしても select は嫌だという方は以下の様に書くと良いです。(注意: ブロックはするので select とは動作が違う事に注意)

if x, ok := <-c; ok {
    // x を使った処理
}

C++ 向けの扱いやすい json ライブラリ μjson

$
0
0

おなじみC/C++から使えるJSONライブラリを紹介するコーナー。まずは過去のまとめ。

僕は C++ では STL が好きなので JSON をパースした後の構造も、std::mapstd::vectorで文字列も std::stringなのが好きです。なので picojson をひたすら使ってきましたが、picojson と同じ様に STL フレンドリな JSON ライブラリが ujson(μjson) です。

awangk / ujson — Bitbucket
https://bitbucket.org/awangk/ujson

特徴は以下の通り。

  • 単純な API で小さなライブラリ
  • 小綺麗なフォーマットでの JSON 出力
  • UTF-8 を扱える速いパーサ
  • 自由なライセンス
  • C++11 フレンドリ

μjson は MIT ライセンスで提供されています。ただし double の変換(IEEE double)に v8 でも使われている double-conversionというライブラリを使用しており、その部分については double-conversion のライセンスに委ねられます。

double の変換を行うという事で、つまり Bignum が扱えます。picojson はヘッダオンリーですが、ujson はビルド済みライブラリをリンクしてビルドします。

#include <iostream>
#include <string>
#include <algorithm>
#include <ujson.hpp>

struct book_t {
  std::string title;
  std::vector<std::string> authors;
  int year;
};

book_t
make_book(ujson::value v) {
  if (!v.is_object())
    throw std::invalid_argument("object expected for make_book");

  book_t book;
  std::vector<std::pair<std::string, ujson::value>> object =
    object_cast(std::move(v));

  auto it = find(object, "title");
  if (it == object.end() || !it->second.is_string())
    throw std::invalid_argument("'title' with type string not found");
  book.title = string_cast(std::move(it->second));

  it = find(object, "authors");
  if (it == object.end() || !it->second.is_array())
    throw std::invalid_argument("'authors' with type array not found");
  std::vector<ujson::value> array = array_cast(std::move(it->second));
  book.authors.reserve(array.size());
  for (auto it = array.begin(); it != array.end(); ++it) {
    if (!it->is_string())
      throw std::invalid_argument("'authors' must be array of strings");
    book.authors.push_back(string_cast(std::move(*it)));
  }

  it = find(object, "year");
  if (it == object.end() || !it->second.is_number())
    throw std::invalid_argument("'year' with type number not found");
  book.year = int32_cast(it->second);

  return book;
}

int
main(int argc, char* argv[]) {
  auto v = ujson::parse(R"(
    {
      "title":"foo",
      "authors":["bar", "baz"],
      "year": 123
    }
  )");
  auto t = make_book(v);
  std::cout << "title:" << t.title << std::endl;
  std::for_each(t.authors.begin(), t.authors.end(), [&](std::string& x) {
    std::cout << "author:" << x << std::endl;
  });
  std::cout << "year:" << t.year << std::endl;
  return 0;
}

オブジェクトにキーが存在した場合に find でイテレータを戻してくれるのでかなりスッキリ書けます。

Re: Golang で言語処理100本ノックの No.3 の問題を解いてベンチマークとってみた

$
0
0
Golang で言語処理100本ノックの No.3 の問題を解いてベンチマークとってみた - Qiita
http://qiita.com/megu_ma/items/01029c3ca24fb9820373

正規表現や strings.Trim等はとても便利なのですが、いかんせん単一の関数呼び出しに対して真面目にループを回す作りになっており、戻り値がコピーされる前提の作りになっています。例えば

func answer1() []int {

    // 単語に分解
    words := strings.Split(sentence, " ")

    var pi []int
    for _, word := range words {
        word = strings.Trim(word, ",")
        word = strings.Trim(word, ".")
        // pi = append(pi, utf8.RuneCountInString(word))
        pi = append(pi, len(word))

    }

    return pi

}

このコードの場合

  1. strings.Splitで文字列の先頭から終端まで検査しながらループ
  2. wordsで単語数分ループ(W)
  3. strings.Trimで単語の長さ分ループ(W × 2)

という無駄なループが回ります。正規表現に至っても同じ。

func answer2() []int {

    // 文を正規表現で単語に分解
    words := regexp.MustCompile(`\s|"|,|\.`).Split(sentence, -1)

    var pi []int
    for _, word := range words {
        // 文末がスプリットされた場合、words の配列の最後に空文字が入る
        if word != "" {
            // pi = append(pi, utf8.RuneCountInString(word))
            pi = append(pi, len(word))
        }
    }

    return pi

}
  1. パターン文字列長分ループ
  2. 検査対象文字列に対して Splitでマッチングループ
  3. wordsで単語数分ループ(W)

となります。これを1度のループ(lenはカウントしない物とします)で回す事が出来ます。

func answer3() []int {

    var pi []int
    var prev, i int
    b := []byte(sentence)
    l := len(b)
    for i < l {
        for ; i < l; i++ {
            if ('a' <= b[i] && b[i] <= 'z') || ('A' <= b[i] && b[i] <= 'Z') {
                break
            }
        }
        if i < l {
            prev = i
            for ; i < l; i++ {
                if !(('a' <= b[i] && b[i] <= 'z') || ('A' <= b[i] && b[i] <= 'Z')) {
                    break
                }
            }
            pi = append(pi, i-prev)
        }
        i++
    }

    return pi

}

ループ内にループがあり、一見は二重ループが回っている様に見えますが、実際には文字列長に対して先頭位置から終端位置、つまりこのテストケース Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.で言えば 94 文字分しか処理が行われません。

  1. 文字列長分ループ

だけとなります。一見読みづらいかもしれませんが

  • コピーを減らす
  • ループ回数を減らす

これを守ると速いプログラムが書ける様になります。

PASS
BenchmarkAnswer1          300000              5340 ns/op
BenchmarkAnswer2          100000             21620 ns/op
BenchmarkAnswer3         3000000               552 ns/op
ok      _/C_/dev/go-bench       6.508s

僕の環境だとおよそ10倍速い結果となりました。


追記

methane さんがもうちょっと綺麗なのを書いてくれました。

func answer4(s string) []int {
    res := make([]int0len(s)/6)
    cnt := 0
    for _, c := range s {
        switch {
        case 'a' <= c && c <= 'z''A' <= c && c <= 'Z':
            cnt++
        default:
            if cnt != 0 {
                res = append(res, cnt)
                cnt = 0
            }
        }
    }
    if cnt != 0 {
        res = append(res, cnt)
        cnt = 0
    }
    return res
}

SQLite3 で generate_series(連番)

$
0
0

PostgreSQL だと、generate_series という集合生成関数があり、例えば

postgres=# select generate_series(1, 10) x;
 x
----
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
(10 行)

こういう連番がサックリと作れる訳なのですが、SQLite3 にはこの様な関数が用意されていません。かといって、この generate_series の為に SQLite3 拡張を作って enable_load_extension する(例: Big Sky :: SQLite で twitter のタイムラインを select する。)のはちょっと辛い。しかしながらどうしても必要な場面というのは出てきます。例えば FizzBuzz とか FizzBuzz とか、あと FizzBuzz なんかでも必要ですね。

そういう場合に使えるのが with recursiveです。

SQLite Query Language: WITH clause

A recursive common table expression can be used to write a query that walks a tree or graph

https://www.sqlite.org/lang_with.html#recursivecte

これを使うと、通常の SQL 文に再帰属性を付与する事が出来ます。

with recursive
generate_series(x) as (
 select 1
 union all
 select x+1 from generate_series limit 12
select x from generate_series;

関数的としても扱えませんし、少し面倒臭いですが無いよりはマシです。

これを使えば FizzBuzz も簡単。

with recursive
generate_series(x) as (
 select 1
 union all
 select x+1 from generate_series limit 100
select
  case 
  when 0 = x % 15 
  then 'FizzBuzz' 
  when 0 = x % 5 
  then 'Buzz' 
  when 0 = x % 3 
  then 'Fizz' 
  else ''||x
  end
from
  generate_series

SQL Fiddle による実行結果でもちゃんと動いています。便利ですね。

golang の自然文字列ソート関数書いた

$
0
0

とは言っても焼き直しですが

Natural Order String Comparison

Natural Order String Comparison by Martin Pool Computer string sorting algorithms generally don't or...

http://sourcefrog.net/projects/natsort/

こにらにあった natsort を golang で焼きなおしてみました。

mattn/natural - GitHub
https://github.com/mattn/natural
package main

import (
    "fmt"
    "github.com/mattn/natural"
)

func main() {
    a := []string{
        "3",
        "1",
        "10",
        "2",
        "20",
    }
    natural.Sort(a)
    for _, s := range a {
        fmt.Println(s) // 1 2 3 10 20 
    }
}

ファイル名をソートする場合等に便利そうです。

man ページを読むには人生は短すぎるのでコマンドラインからチートシートを引こう

$
0
0
dufferzafar/cheat - GitHub

Cheatsheets for command line, because, you know, life is too short to read manpages.

https://github.com/dufferzafar/cheat

人生は短い。

$ go get github.com/dufferzafar/cheat

してインストール。ホームディレクトリ直下に .cheatsheetsというフォルダを作り、そこに

jahendrie/cheat - GitHub
https://github.com/jahendrie/cheat

このリポジトリにある dataフォルダ内のファイルをコピーすればよろし。

cheat - Create and view command-line cheatsheets.

Version: 0.5

Usage:
    cheat <command> [cheatsheet] [<args>]


Commands:
    show        Show cheats related to a command
    edit        Add/Edit a cheat
    list        List all available cheats
    config      Edit the config file
    help        Shows a list of commands or help for one command


Global Options:
    --help, -h          show help
    --version, -v       print the version


Examples:
    cheat show git              Shows git cheatsheet
    cheat show git -c 12        Copy the 12th git cheat

    cheat edit at               Edit cheatsheet named at
$ cheat edit curl

でエディタが起動するので自分で追加する事も可能。Windows 対応もしておきましたので、その内マージされるかも。

Support Windows by mattn · Pull Request #1 · dufferzafar/cheat - GitHub
https://github.com/dufferzafar/cheat/pull/1

江添さんに簡単に質問出来るコマンドを golang で書いた。

$
0
0

C++er ならみんな見てるいう噂の江添さんの ask.me。見ているだけも面白いですが、質問もしたくなりますよね。

Ryou Ezoe | ask.fm/EzoeRyou
http://ask.fm/EzoeRyou

質問したいだけなのにいちいちブラウザ起動するのもダルいので、コマンドラインから質問を投げられるコマンド「ezoe」を作りました。

mattn/ezoe · GitHub
https://github.com/mattn/ezoe

インストールは簡単で golang が入ってる状態なら

$ go get github.com/mattn/ezoe

これだけで ok。使い方も簡単で

$ ezoe

とすると質問の一覧が色付きで表示される。

しかも「質問ではない」と「自由」がハイライトされるので江添フリークも安心。そして引数を付けて起動すると ask.me に質問を投げられます。

$ ezoe 僕は梅干しが大好きです

あとは江添さんの回答を待つだけ。「質問ではない」って返事が返ってきたら素直に喜びましょう。

オマケ機能ですが、-uフラグで江添さん以外の人にも使えます。

$ ezoe -u mattn_jp golangをご覧ください

質問ではない。

lingr で stackverflow を検索出来る bot を golang で書いた。

$
0
0

slack から stackoverflow を検索出来る slack-overflow の lingr 版を作ってみた。

karan/slack-overflow - GitHub
https://github.com/karan/slack-overflow
akechi/stackoverflow-lingrbot - GitHub
https://github.com/akechi/stackoverflow-lingrbot

今回はなんとなく gin で書いた。

Gin Web Framework

Low Overhead Powerful API You can add global, per-group, and per-route middlewares, thousands of nes...

https://gin-gonic.github.io/gin/

コード短いのでのっけておく。

package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "net/http"
    "net/url"
    "os"
    "regexp"

    "github.com/gin-gonic/gin"
    "github.com/mattn/go-lingr"
)

type resp struct {
    Items []struct {
        Tags  []string `json:"tags"`
        Owner struct {
            Reputation   int    `json:"reputation"`
            UserID       int    `json:"user_id"`
            UserType     string `json:"user_type"`
            ProfileImage string `json:"profile_image"`
            DisplayName  string `json:"display_name"`
            Link         string `json:"link"`
        } `json:"owner"`
        IsAnswered       bool   `json:"is_answered"`
        ViewCount        int    `json:"view_count"`
        AnswerCount      int    `json:"answer_count"`
        Score            int    `json:"score"`
        LastActivityDate int    `json:"last_activity_date"`
        CreationDate     int    `json:"creation_date"`
        QuestionID       int    `json:"question_id"`
        Link             string `json:"link"`
        Title            string `json:"title"`
    } `json:"items"`
    HasMore        bool `json:"has_more"`
    QuotaMax       int  `json:"quota_max"`
    QuotaRemaining int  `json:"quota_remaining"`
}

var re = regexp.MustCompile(`^stackoverflow(?:\w+) (.+)$`)

func defaultAddr() string {
    port := os.Getenv("PORT")
    if port == "" {
        return ":80"
    }
    return ":" + port
}

var addr = flag.String("addr", defaultAddr(), "server address")

func main() {
    flag.Parse()

    r := gin.Default()

    r.GET("/"func(c *gin.Context) {
        c.String(200"")
    })
    f := func(c *gin.Context) {
        site := c.Params.ByName("site")
        if site == "" {
            site = "stackoverflow"
        }
        var status lingr.Status
        if !c.EnsureBody(&status) {
            return
        }
        urls := ""
        for _, event := range status.Events {
            message := event.Message
            if message == nil {
                continue
            }
            if !re.MatchString(message.Text) {
                continue
            }
            question := re.FindStringSubmatch(message.Text)[1]
            params := url.Values{}
            params.Add("intitle", question)
            params.Add("site", site)
            params.Add("sort""activity")
            params.Add("order""desc")
            res, err := http.Get("https://api.stackexchange.com/2.2/search?" + params.Encode())
            println("https://api.stackexchange.com/2.2/search?" + params.Encode())
            if err != nil {
                println(err.Error())
                continue
            }
            defer res.Body.Close()
            var resp resp
            if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
                println(err.Error())
                continue
            }
            for _, item := range resp.Items {
                u := item.Link
                if len(u) > 300 {
                    u = fmt.Sprintf("http://%s.com/q/%d", site, item.QuestionID)
                }
                s := fmt.Sprintf("%s\n%s\n", item.Title, u)
                println(s)
                if len(urls+s) > 1000 {
                    break
                }
                urls += s
            }
        }
        c.String(200, urls)
        return
    }
    r.POST("/", f)
    r.POST("/:site", f)
    r.Run(*addr)
}

僕がこういうのを書く時は、一度 JSON を何等かの方法で得て

JSON-to-Go: Convert JSON to Go instantly

JSON-to-Go Convert JSON to Go struct This tool instantly converts JSON into a Go type definition. Pa...

http://mholt.github.io/json-to-go/

このサイトで golang の struct に変換、クライアント処理として stackoverflow の検索処理を実装しながら最後に Web のガワを付けていく。実質20~30分程度で出来た。

困ったのが stackoverflow の日本語版にも対応したのだけど、日本語版は URL にタイトルの日本語が含まれていて平気で1000文字を超える。しかし lingr の API は1000文字までしか発言出来なくて(bot triggered アクションで分割送信なら可能)困った。どうやら URL の日本語部分は無くてもアクセス出来るのが分かったので300文字(適当)超えたらそっちを使う様に対応した。

lingr だと stackoverflow という名前で居ますので invite して「stackoverflow jquery」とか「stackoverflowja ウェブ」とか発言すると反応します。

適当な日本人の名前や住所をランダムに作れる gimei を golang に port した。

$
0
0
willnet/gimei - GitHub

gimei は、日本人の名前や、日本の住所をランダムに返すライブラリです。テストの時などに使います。似たようなライブラリにfakerがあります。fakerはとても優れたライブラリで、多言語対応もしていますが、ふりがな(フリガナ)は流石に対応していません。gimei ふりがな(及びフリガナ)に対応しています。

https://github.com/willnet/gimei

オリジナルは ruby gems です。

mattn/go-gimei - GitHub

golang port of gimei

https://github.com/mattn/go-gimei

作者の方に ok を貰ったのでデータも同梱しています。使い方もほぼ同じです。

package main

import (
    "fmt"

    "github.com/mattn/go-gimei"
)

func main() {
    name := gimei.NewName()
    fmt.Println(name)                  // 斎藤 陽菜
    fmt.Println(name.Kanji())          // 斎藤 陽菜
    fmt.Println(name.Hiragana())       // さいとう はるな
    fmt.Println(name.Katakana())       // サイトウ ハルナ
    fmt.Println(name.Last.Kanji())     // 斎藤
    fmt.Println(name.Last.Hiragana())  // さいとう
    fmt.Println(name.Last.Katakana())  // サイトウ
    fmt.Println(name.First.Kanji())    // 陽菜
    fmt.Println(name.First.Hiragana()) // はるな
    fmt.Println(name.First.Katakana()) // ハルナ
    fmt.Println(name.IsMale())         // false

    male := gimei.NewMale()
    fmt.Println(male)            // 小林 顕士
    fmt.Println(male.IsMale())   // true
    fmt.Println(male.IsFemale()) // false

    address := gimei.NewAddress()
    fmt.Println(address)                       // 岡山県大島郡大和村稲木町
    fmt.Println(address.Kanji())               // 岡山県大島郡大和村稲木町
    fmt.Println(address.Hiragana())            // おかやまけんおおしまぐんやまとそんいなぎちょう
    fmt.Println(address.Katakana())            // オカヤマケンオオシマグンヤマトソンイナギチョウ
    fmt.Println(address.Prefecture)            // 岡山県
    fmt.Println(address.Prefecture.Kanji())    // 岡山県
    fmt.Println(address.Prefecture.Hiragana()) // おかやまけん
    fmt.Println(address.Prefecture.Katakana()) // オカヤマケン
    fmt.Println(address.Town)                  // 大島郡大和村
    fmt.Println(address.Town.Kanji())          // 大島郡大和村
    fmt.Println(address.Town.Hiragana())       // おおしまぐんやまとそん
    fmt.Println(address.Town.Katakana())       // オオシマグンヤマトソン
    fmt.Println(address.City)                  // 稲木町
    fmt.Println(address.City.Kanji())          // 稲木町
    fmt.Println(address.City.Hiragana())       // いなぎちょう
    fmt.Println(address.City.Katakana())       // イナギチョウ

    prefecture := gimei.NewPrefecture()
    fmt.Println(prefecture) // 青森県
}

注意点としては漢字の名前に対して読みが複数ある場合もあります。例えば上記の「陽菜」だと「はな」と「はるな」の2つがあります。Stringer の実装は漢字出力にしています。テストの際にお役立て下さい。

Vim scriptで「ソフトウェアエンジニアならば1時間以内に解けなければいけない5つの問題」を解いてみた

$
0
0

問題1

scriptencoding utf-8

"forループ、whileループ、および再帰を使用して、リスト内の数字の合計を計算する
"3つの関数を記述せよ。

functions:problem1_1()
  let l = [3,5,1,2,9]
  let s = 0
  for i in l
    let s += i
  endfor
  return s
endfunction

functions:problem1_2()
  let l = [3,5,1,2,9]
  let s = 0
  let i = 0
  while i < len(l)
    let s += l[i]
    let i += 1
  endwhile
  return s
endfunction

functions:_problem1_3(s, l)
  if len(a:l)
    return a:s + a:l[0+ s:_problem1_3(a:sa:l[1:])
  endif
  return a:s
endfunction

functions:problem1_3()
  let l = [3,5,1,2,9]
  return s:_problem1_3(0, l)
endfunction

if s:problem1_1() != 20
  throw "プログラマ失格: s:problem1_1"
endif

if s:problem1_2() != 20
  throw "プログラマ失格: s:problem1_2"
endif

if s:problem1_3() != 20
  throw "プログラマ失格: s:problem1_3"
endif

問題2

scriptencoding utf-8

" 交互に要素を取ることで、2つのリストを結合する関数を記述せよ。例えば
" [a, b, c]と[1, 2, 3]という2つのリストを与えると、関数は [a, 1, b, 2, c, 3]
" を返す。

functions:_problem2(lhs, rhs)
  let r = []
  for i in range(len(a:lhs))
    call add(r, a:lhs[i])
    call add(r, a:rhs[i])
  endfor
  return r
endfunction

functions:problem2()
  let lhs = ["a""b""c"]
  let rhs = [123]
  return s:_problem2(lhs, rhs)
endfunction

if s:problem2() !=# ["a"1"b"2"c"3]
  throw "プログラマ失格: s:problem2"
endif

問題3

scriptencoding utf-8

" 最初の100個のフィボナッチ数のリストを計算する関数を記述せよ。定義では、フィ
" ボナッチ数列の最初の2つの数字は0と1で、次の数は前の2つの合計となる。例えば最
" 初の10個のフィボナッチ数列は、0, 1, 1, 2, 3, 5, 8, 13, 21, 34となる。

functions:fib(n)
  let r = [01]
  let i = 2
  while i < a:n
    call add(r, r[i-1+ r[i-2])
    let i += 1
  endwhile
  return r
endfunction

functions:problem3()
  return s:fib(100)
endfunction

if s:problem3() !=# [112358132134558914423337761098715972584418167651094617711286574636875025121393196418317811514229832040134626921783093524578570288792274651493035224157817390881696324598610233415516558014126791429643349443770140873311349031701836311903297121507348075269767778742049125862690252036501107432951280099533162911738626757127213958386244522585143371736543529616259128672987995672202604115480087559202504730781961405273953788165574703198421061020985772317167680177565277778900352884494557021285372723460248141117669030460994190392490709135308061521170129498454011879264806515533049393130496954492865721114850779780503416454622906707552793970088475789443943237914641447233402467622123416728348467685378890623731439066130579072161159199194853094755497160500643816367088259695496911122585420196140727489673679891637638612258110008777836610193117799794160047141892880067194370816120466004661037553030975401138047463464291220016041512187673819740274219868223167319404346349900999055168070885485832307283621143489848422977135301852344706746049218922995834555169026354224848179261915075]
  throw "プログラマ失格: s:problem3"
endif

問題4

scriptencoding utf-8

" 正の整数のリストを与えられたとき、数を並び替えて可能な最大数を返す関数を記述
" せよ。例えば、[50, 2, 1, 9]が与えられた時、95021が答えとなる。

functions:compare(lhs, rhs)
  return a:rhs . a:lhs < a:lhs . a:rhs ? -1 : 1
endfunction

functions:problem4()
  let l = [50219]
  return join(sort(l, function('s:compare'))'')
endfunction

if s:problem4() !=# 95021
  throw "プログラマ失格: s:problem4"
endif

問題5

scriptencoding utf-8

" 1,2,…,9の数をこの順序で、"+"、"-"、またはななにもせず結果が100となるあらゆ
" る組合せを出力するプログラムを記述せよ。
" 例えば、1 + 2 + 34 - 5 + 67 - 8 + 9 = 100となる。

functions:make100(r, b, n) abort
  if a:n == 10
    if eval(a:b) == 100
      call add(a:ra:b)
    endif
  else
    for op in ['+''-''']
      call s:make100(a:ra:b . op . (a:n)a:n+1)
    endfor
  endif
  return ''
endfunction

functions:problem5()
  set maxfuncdepth=200
  let r = []
  call s:make100(r, 12)
  return r
endfunction

for s:l in s:problem5()
  if eval(s:l) != 100
    throw "プログラマ失格: s:problem5"
  endif
endfor
unlet s:l

で、問題3 なのですが Vim script では 32bit int しか扱えないので100個のフィボナッチ数列が数えられません。

プログラマ失格

よってわたくし、プログラマ失格となりました。みなさん今までお世話になりました。


何もソースコードを変更せずに2038年問題を解決する。

$
0
0

まず2038年問題というのをご存じでしょうか

2038年問題 - Wikipedia

2038年問題(にせんさんじゅうはちねんもんだい)は、2038年1月19日3時14分7秒(UTC、以下同様)を過ぎると、コンピュータが誤動作する可能性があるとされる年問題。

http://ja.wikipedia.org/wiki/2038%E5%B9%B4%E5%95%8F%E9%A1%8C

C言語で epoch を扱う time_t が32bit OS上でオーバーフローし日本時間2038/01/19 12:14:07の次に1901/12/14 05:45:52が来てしまうという問題です。まぁそもそも2038年にもなって32bit OSを使っている側が悪いと言えばアレですが。

実際にどんな事が起きるかは、32bit OS上で以下を実行すれば分かる。

#include <stdio.h>
#include <time.h>

void
test(time_t tmr) {
  struct tm* t;
  char buf[2038];
  t = localtime(&tmr);
  strftime(buf, sizeof(buf), "%Y/%m/%d %H:%M:%S", t);
  puts(buf);
}

int
main(int argc, char* argv[]) {
  int i;
  time_t tmr = 2147483647;

  test(tmr); /* 2038/01/19 12:14:07 */
  tmr += 1;
  test(tmr); /* 1901/12/14 05:45:52 */
  return 0;
}

実行結果

$ ./a.out
2038/01/19 12:14:07
1901/12/14 05:45:52

でも待てよ...と思っていたら同じ事を考えてる人がいた。

Cのライブラリの2038年問題と、その対策方法(いや結局time_tは使わないってのもあり)

...という方法でラップすれば2106年迄使う事が出来ます。

http://www.tensyo.com/urame/date/Y2038.htm

これを使えば time_t がオーバーフローした後でも localtime が正しく動く!誰も困らない!2037年頃にオッサンが招集されて狭い部屋に閉じ込められるという事案も発生しない!という事で作ってみた。

mattn/localtime2038 - GitHub
https://github.com/mattn/localtime2038
#ifdef __i386__
#define _GNU_SOURCE
#include <time.h>
#include <dlfcn.h>

static struct tm* (*__localtime)(const time_t *timer);

__attribute__((constructor))
void
wrap_localtime() {
  __localtime = dlsym(RTLD_NEXT, "localtime");
}

struct tm *
localtime(const time_t *timer) {
  struct tm* t;
  unsigned long tmr = *timer;
  int yadd = 0;

  while (tmr >= ((60uL*60uL*24uL)*24837uL)) {
    yadd  +=28;
    tmr -= (60uL*60uL*24uL)*10227uL;
  }
  t = __localtime((time_t*)&tmr);
  t->tm_year += yadd;
  return t;
}
#endif

これを使って LD_PRELOADを付けて実行すれば

$ LD_PRELOAD=./liblocaltime2038.so ./a.out
2038/01/19 12:14:07
2038/01/19 12:14:08

おぉぉぉぉ...これで2038年にオッサンが過労死する事もなくなりますね。良かったですね。

ジョークです。ちゃんと64bitに移行しましょう。

2038年南海トラフの巨大地震2038年南海トラフの巨大地震
尾池和夫
マニュアルハウス / ¥ 2,700 (2015-03-01)
 
発送可能時間:在庫あり。

bashを江添さんっぽくする方法

Vim の CtrlP matcher、cpsm がヤバイくらいに速すぎる

$
0
0

Vim で CtrlPを使っている人もそこそこいるかと思いますが、ファイル検索が終わり文字をタイプした時に行を絞り込む部分(マッチャーと言います)がデフォルトの状態だと Vim script を使って処理される為、ファイル数が多くなるとモッサリして来ます。これを解消する為に幾らかの人が頑張っています。

FelikZ/ctrlp-py-matcher - GitHub

Fast vim CtrlP matcher based on python

https://github.com/FelikZ/ctrlp-py-matcher
JazzCore/ctrlp-cmatcher - GitHub

CtrlP C matching extension

https://github.com/JazzCore/ctrlp-cmatcher
junegunn/fzf - GitHub

A command-line fuzzy finder written in Go

https://github.com/junegunn/fzf

どれも決め手に掛けるなーと思っていたのですが、そんな中に突如現れたのが cpsm です。

nixprime/cpsm - GitHub

A CtrlP matcher, specialized for paths.

https://github.com/nixprime/cpsm

README.md に書いてありますが、cpsm は python 拡張で実装されしかもスレッドで起動します。他のマッチャーでもそうですが文字列のファジーマッチングは意外に処理時間が必要になります。cpsm では使用可能なスレッド数を自動取得した後、ファイル一覧に対して並列に先取りして処理を行います。全ての一覧に対してマッチ結果が得られたらソートして vim 側に返しています。

cpsm/python_extension_main.cc at 00315bdd583f5083a90a6d8c6e38eef2f2d72070 · nixprime/cpsm · GitHub

CtrlP ではファイル一覧の作成には初回だけ時間が掛かりますが、以後は有限時間付きのキャッシュが読み込まれます。マッチャーの速度次第では目にも留まらない速度でファイルを選択出来る様になります。

以下は ruby22 のディレクトリ直下(総ファイル 24685個)を CtrlP で表示し、fluentd.gemspecを開くまでの操作です。

※キャッシュは作成済みです。

cpsm

はえぇ...。しばらく常用しようと思います。

尚、CtrlP ではファイルの一覧についても外部の機能を使う事が出来ますが、僕は filesというツールを使っています。

mattn/files - GitHub

Fast file find

https://github.com/mattn/files

files は golang で書かれており、-aを付ける事で goroutine を使ってディレクトリ階層を下ってファイル一覧を出力します。よろしければこちらもお使い下さい。

CtrlP についてのご意見などあれば、現在 ctrlpvim/ctrlp.vim のメンテナをやってますので twitter 等でお声掛け下さい。

Vim プラグインの機能として golang を使う。

$
0
0

golang 1.5 から、x86_64 のみですが -buildmode=c-sharedというビルドオプションが足される事になりました。

これは、golang で共有ライブラリを生成する為のオプションで、例えば

package main

import (
    "C"
    "fmt"
)

var (
    c chan string
)

func init() {
    c = make(chan string)
    go func() {
        n := 1
        for {
            switch {
            case n%15 == 0:
                c <- "FizzBuzz"
            case n%3 == 0:
                c <- "Fizz"
            case n%5 == 0:
                c <- "Buzz"
            default:
                c <- fmt.Sprint(n)
            }
            n++
        }
    }()
}

//export fizzbuzz
func fizzbuzz(n int) *C.char {
    return C.CString(<-c)
}

func main() {
}

こういう golang のコードlibfizzbuzz.goがあったとして

$ go build -buildmode=c-shared -o libfizzbuzz.so libfizzbuzz.go

この様にビルドすると libfizzbuzz.soが出力されます。python を使えば

from ctypes import *
lib = CDLL("./libfizzbuzz.so")
lib.fizzbuzz.restype = c_char_p
print lib.fizzbuzz()
print lib.fizzbuzz()
print lib.fizzbuzz()
print lib.fizzbuzz()
print lib.fizzbuzz()
print lib.fizzbuzz()
1
2
Fizz
4
Buzz
Fizz

この様に関数を呼ぶ度に FizzBuzz が生成される関数も簡単に作れます。Vim には if_python という python 拡張もありますのでこれを使えば Vim プラグインの機能の一部として golang を使う事が出来る様になります。新しめの Vim であれば if_python はスレッドセーフなので

mattn/vim-go-fizzbuzz - GitHub
https://github.com/mattn/vim-go-fizzbuzz
let s:libdir = expand('<sfile>:p:h:h') . '/go'

if !filereadable(s:libdir . '/libfizzbuzz.so')
  exe "!cd " . s:libdir . " && make"
endif

function! fizzbuzz#start()
python<<EOS
import vim
import thread
import time
from ctypes import *

libfizzbuzz = CDLL(vim.eval('s:libdir') + '/libfizzbuzz.so')
libfizzbuzz.fizzbuzz.restype = c_char_p

def run():
  while True:
    vim.eval("fizzbuzz#on_fizzbuzz('%s')" % libfizzbuzz.fizzbuzz())
    time.sleep(1)

thread.start_new_thread(run, ())
EOS
endfunction

function! fizzbuzz#on_fizzbuzz(s)
  if mode() != 'n'
    return
  endif
  echo a:s
endfunction

1秒に1回、golang の goroutine で非同期にループしながら channel を経由して渡される FizzBuzz を、python のスレッドを介して Vim のメッセージが表示されるという、一見誰の役にも立ちそうにない事ながら未来感が溢れてきますね。どうしても python を書きたくない、でも非同期がやりたい!golang が書きたい!という人にはとても良い時代になってきました。もうすぐ、いろんなライブラリを golang だけで書ける日がやってきそうです。

ちなみに、libcallex-vimというプラグインを使えば

let fizzbuzz = libcallex#load("/home/mattn/dev/go-sandbox/libfizzbuzz.so")
echo fizzbuzz.call("fizzbuzz", [], "string")
echo fizzbuzz.call("fizzbuzz", [], "string")
echo fizzbuzz.call("fizzbuzz", [], "string")
echo fizzbuzz.call("fizzbuzz", [], "string")
echo fizzbuzz.call("fizzbuzz", [], "string")
echo fizzbuzz.call("fizzbuzz", [], "string")
if_python も使わなくても(ただしCライブラリのビルドが必要) FizzBuzz 出来ますね。

Crystal と CRuby でHTTPサーバのベンチマーク

$
0
0

Matz も驚く Crystal


Crystal

Language Goals Ruby-inspired syntax. Statically type-checked but without having to specify the type ...

http://crystal-lang.org/

ruby と殆ど同じシンタックスが通る様です。

# A very basic HTTP server
require "http/server"

server = HTTP::Server.new(8080do |request|
  HTTP::Response.ok "text/plain""Hello world!"
end

puts "Listening on http://0.0.0.0:8080"
server.listen

しかも native code が吐けるらしい。ならばとベンチマークを取ってみた。CRuby 側は以下の sinatra。CRuby は 2.2.0p0 を使用。

require "sinatra"

set :environment:production

get "/" do
  content_type 'text/plain'
  "Hello world!"
end
$ ab -k -c 10 -n 10000 http://localhost:8000

まずは sinatra

Server Hostname:        localhost
Server Port:            4567

Document Path:          /
Document Length:        12 bytes

Concurrency Level:      10
Time taken for tests:   8.502 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    10000
Total transferred:      1620000 bytes
HTML transferred:       120000 bytes
Requests per second:    1176.24 [#/sec] (mean)
Time per request:       8.502 [ms] (mean)
Time per request:       0.850 [ms] (mean, across all concurrent requests)
Transfer rate:          186.08 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:     1    8   2.6      8      66
Waiting:        1    8   2.6      8      66
Total:          1    8   2.6      8      67

Percentage of the requests served within a certain time (ms)
  50%      8
  66%      9
  75%      9
  80%     10
  90%     11
  95%     13
  98%     14
  99%     16
 100%     67 (longest request)

そして Crystal

追記: 以下はデバッグビルドの結果でした。その下に続けてリリースビルド時の結果を追記しています。

デバッグビルド

Server Hostname:        localhost
Server Port:            8080

Document Path:          /
Document Length:        12 bytes

Concurrency Level:      10
Time taken for tests:   0.633 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    10000
Total transferred:      1010000 bytes
HTML transferred:       120000 bytes
Requests per second:    15802.98 [#/sec] (mean)
Time per request:       0.633 [ms] (mean)
Time per request:       0.063 [ms] (mean, across all concurrent requests)
Transfer rate:          1558.69 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       1
Processing:     0    1   0.2      1       3
Waiting:        0    1   0.2      1       3
Total:          0    1   0.2      1       3

Percentage of the requests served within a certain time (ms)
  50%      1
  66%      1
  75%      1
  80%      1
  90%      1
  95%      1
  98%      1
  99%      1
 100%      3 (longest request)

リリースビルド

Server Software:        
Server Hostname:        localhost
Server Port:            8080

Document Path:          /
Document Length:        12 bytes

Concurrency Level:      10
Time taken for tests:   0.295 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    10000
Total transferred:      1010000 bytes
HTML transferred:       120000 bytes
Requests per second:    33888.54 [#/sec] (mean)
Time per request:       0.295 [ms] (mean)
Time per request:       0.030 [ms] (mean, across all concurrent requests)
Transfer rate:          3342.52 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       1
Processing:     0    0   0.1      0       1
Waiting:        0    0   0.1      0       1
Total:          0    0   0.1      0       1

Percentage of the requests served within a certain time (ms)
  50%      0
  66%      0
  75%      0
  80%      0
  90%      1
  95%      1
  98%      1
  99%      1
 100%      1 (longest request)

比べ物にならなかった。ただ、h2ohttp-serverだとファイルをサーブしたとしても 28000req/sec から 30000req/sec は出る環境なので、Crystal のネイティブコードがめちゃくちゃ速いという訳ではない。

Viewing all 121 articles
Browse latest View live