としおの読書生活

田舎に住む社会人の読書記録を綴ります。 主に小説や新書の内容紹介と感想を書きます。 読書の他にもワイン、紅茶、パソコン関係などの趣味を詰め込んだブログにしたいです。

2021年03月

3884509_s

研究者であり日本のライフハックの先駆者である、堀正岳さんの『ライフハック大全ー人生と仕事を変える小さな習慣250』を読みました。

その中で個人的にすぐに真似してみたいと思ったライフハックを7つ紹介していきます。




作業を「下り坂」でパーキングする


毎日の作業をいつもきりの良いところで終わらせようとする人は多いのではないのでしょうか。

これは気分が良いことかもしれませんが、明日以降にも引き続き同じ作業を続ける場合は、注意が必要です。

次の日に新しいタスクを始めようとすると気が重くなかなか手がつかないことがあります。

この問題を解決するために、1日の作業をあえて中途半端なところで切り上げましょう

こうすることで、次の日に作業を気持ちよく始めることができます。

また下り坂でやめることで、ダラダラときりの良いところまで残業しようとする、無駄な残業時間も減らすことができます。



48:12時間分割法


どんなにすごい人でも一日に集中することができる時間は限られています。

そこで、集中力をマラソンランナーのように長い時間維持するテクニックを教えます。

そのテクニックは48:12時間分割法です。

これは名前のままで1時間の作業時間を48分の集中時間と12分の休息時間に分けるという方法です。

48分集中した後に12分休息をとることで、次の1時間のサイクルを始めるときに集中力を回復させることができます。

このテクニックを使う上で大切なことは、調子がよくても休息するときと作業を行う時間をしっかりと分けることです。

その時は、調子がいいかもしれませんが無理をしているだけにすぎないのでいずれ集中力が途切れるでしょう。

また、48分と12分という時間配分はあくまで例です。人によっては50分作業して10分休憩や、30分作業して10分休憩などバランスが違います。

自分に最適な時間を見つけましょう。



怒りを制御するための3つのテクニック


いくら怒らないようにしていても予想外の出来事なので怒りを覚えることがあります。

ここでは、怒りを抑えるための3つのテクニックを紹介していきます。


1. 怒りそうになったら手のひらをパーにする

怒りを感じると緊張して握り拳を作りがちですが、これを意識的にパーにすることで緊張が和らぎます。


2. 肩を下げる

手のひらと同様に怒っている時は、緊張して肩が上がりがちです。

意識して肩を下げるようにしてリラックスの姿勢をとりましょう。


3. 話すスピードを落とす

怒りが、込み上げると早口になりがちです。意識的にゆっくりと話すようにしましょう。

ゆっくりと話すことで、話し声も小さくなり冷静さを取り戻すことができます。






大量の悪いアイデアをつくる


優秀な人でも大量の良いアイデアを生み出すのは難しいです。

一方、大量の悪いアイデアを生み出すことはそこまで難しくありません。

実は悪いアイデアを生み出すというのは、良いアイデアを生み出すための鍵です。

毎朝5つのアイデアを書き留める習慣を作りましょう。

毎朝考えていると、ひどいアイデアも大量に生まれます。しかし、こうして様々なアイデアを組み合わせていくことで誰にも辿り着かない発想に行き着くことがあるでしょう。

あのエジソンですら、アイデアを生み出す量に拘っていたのですからきっとあなたも大量のアイデアを生み出せばいつか良いものが見つかるでしょう。



利き手と逆の手を使ってマインドフルネスを体感する


マインドフルネスとは、現在起こっていることに意識を集中させる瞑想法で、ストレスを軽減することや様々な心理的な症状に効果があることが知られています。

しかし、瞑想ができるようになるためには練習が必要です。

そこで、普段利き手として使っている腕とは逆の腕で、日常の活動を行ってみましょう

利き腕と違う手を使うことで、掃除をするだけでも全てを意識的に注意深く行わなければうまくできません。

利き手では自動化していた行動を集中して行うことでマインドフルネスに近い精神状態を生み出すことができます。

マインドフルネスの状態を体感して心の健康を守りましょう。



習慣のトリガーとして優秀なのは「時間」と「場所」


新しい習慣を定着させるときに大切なことは、毎日必ず発生するものをトリガーに設定し、その流れで実行する行動だけを入れ替えるという作戦です。

例えば夕食終わりに必ずテレビをつけるという習慣があるとします。

この流れを脱線させて別の行動をとることで新しい習慣をつくることができます。

夕食後に毎日風呂掃除をするようにすると次第にテレビをつける習慣が風呂掃除をする習慣へと変化していくでしょう。

こうしたトリガーをきっかけに新しい習慣をつくるようにしましょう。



「30日チャレンジ」で人生を楽しくかえてゆく


みなさん新しいことを初めてみたいけどなかなか始めることができないと困っていませんか?

そこで「30日チャレンジ」をしてみましょう。

「30日チャレンジ」とは人生でずっとやってみたかったことを、次の30日間だけ生活に取り入れてみることです。

30日という数字が良い数字で、30日あればちょうど新しい習慣を身につけることができ、「30日チャレンジ」の後も継続的に行うことができます。

一方ずっとやってみたかったことでも、30日すら続けることができなければそれはそこで辞めてしまっても良いことです。

とりあえず30日だけと思って新しいことをやってみましょう。



まとめ


『ライフハック大全ー人生と仕事を変える小さな習慣250』では上記で紹介した他にも様々なライフハックが紹介されています。

本書の後書きでも書かれていたのですが、ライフハックは全て真似しないといけないわけではありません。

自分が真似したいと思うライフハックだけを取り出して、実践していき自分の生活を少しでも良いものにしていきましょう。






3753817_s


以前、以下の記事で作成したSingletonパターンがスレッドセーフではなかったため、本記事ではスレッドセーフなSingletonパターンを実装していきます。





Singletonパターンのクラス構成


今回作成する、スレッドセーフなSingletonパターンのクラス構成は以下のとおりです。

SingletonThreadSafe


Singletonパターンを実装する場合、インスタンスの作成をgetInstanceメソッドで行うということをしがちですが、この場合複数個のスレッドから同時に呼ばれたときに別のインスタンスが作成されるという問題がありました。

そこで今回は新たにcreateメソッドを実装して、ここでインスタンスを作成することでスレッドセーフなSingletonパターンを作成します。

また新しくインスタンスを破棄するdestroyメソッドも追加しました。





C++による実装


サンプルとして複数のスレッドからgetInstanceメソッドを呼ばれても一つしかインスタンスを作成しないということを証明するプログラムを作成します。


Singletonクラス



ヘッダファイル

#ifndef H_SINGLETON
#define H_SINGLETON

class Singleton{
private:
    Singleton();
    ~Singleton();

public:
    static void create();
    static void destroy();
    static Singleton* getInstance();

private:
    static Singletonm_singleton;
};
#endif // H_SINGLETON


ソースファイル

#include <iostream>
#include "Singleton.h"

SingletonSingleton::m_singleton = nullptr;

Singleton::Singleton()
{
    std::cout << "インスタンスを生成しました" << std::endl;
}

Singleton::~Singleton(){
    std::cout << "インスタンスを削除しました" << std::endl;
}

void Singleton::create(){
    if (!m_singleton){
        m_singleton = new Singleton();
    }
}

void Singleton::destroy(){
    if (m_singleton){
        delete m_singleton;
        m_singleton = nullptr;
    }
}

Singleton* Singleton::getInstance(){
    if (!m_singleton){
        return m_singleton;
    }
    return nullptr;
}



main関数


上記で作成したSingletonクラスを実際に使用してみます。

今回Windows環境で作成したため、スレッドはWin32 APIを使用して作成しています。


ソースファイル

#include <iostream>
#include <windows.h>
#include "Singleton.h"

#define LOOP 10
#define THREADNUM 10

struct stThreadArg{
    unsigned long threadNum;
};

DWORD WINAPI thread(LPVOID *arg){
    //struct stThreadArg* threadArg = (struct stThreadArg*)arg;
    unsigned longthreadArg = (unsigned long*)arg;
    std::cout << "Start thread" << *threadArg << std::endl;
    for(int i=0i<LOOP; ++i){
        Singleton::getInstance();
    }
    std::cout << "End thread" << *threadArg << std::endl;
    return 0;
}


int main(void){
    std::cout << "Start" << std::endl;
    
    // インスタンスを作成
    Singleton::create();
    
    /* スレッドの生成 */
    HANDLE handle[THREADNUM];
    unsigned long threadArg[THREADNUM];
    for (int i=0i<THREADNUM; ++i){
        threadArg[i] = i;
        handle[i] = CreateThread(00, (LPTHREAD_START_ROUTINEthread, &threadArg[i], 0NULL);
    }
    WaitForMultipleObjects(THREADNUMhandle,TRUEINFINITE);

    // インスタンスを削除
    Singleton::destroy();
    
    std::cout << "End" << std::endl;

    return 0;
}


実行結果

Start
インスタンスを生成しました
Start threadStart thread2
End thread2
Start thread9
End thread9
Start thread4
End thread4
Start thread3
End thread3
Start thread7
End thread7
Start thread8
End thread8
0
End thread0
Start thread6
End thread6
Start thread5
End thread5
Start thread1
End thread1
インスタンスを削除しました
End


実行結果からインスタンスの生成がcreateメソッドを呼んだときしか行っていないことが分かりますね。

また、destroyメソッドによってインスタンスの削除もしっかり行われています。



まとめ


Singletonパターンを作成するときは、スレッドセーフではない実装を行うようにしましょう。

もし仮にスレッドセーフではないSingletonパターンを作成したとして、不具合が起きていない場合、それはたまたま不具合が起きなかっただけにすぎません。

できる限り不具合が発生する要因をなくしていきましょう。







3753817_s

本記事では、デザインパターンの名著である結城浩さんの『Javaで学ぶデザインパターン入門』を参考にSingletonパターンをC++で実装していきます。



Singletonパターンとは


プログラムを作成するとき、同じクラスのインスタンスを複数作成することがあります。

しかし、中にはこのクラスのインスタンスは、たった1つしか作らないし、作りたくないというものも存在します。

注意深くプログラムを作成してインスタンスを1つしか作らないという方法もありますが、これはかなり手間のかかる作業です。

プログラマが注意してインスタンスが1個しか生成されないプログラムをつくるのではなく、

  • 指定したクラスのインスタンスが絶対に1個しか存在しないことを保証したい
  • インスタンスが1個しか存在しないことをプログラム上で表現したい

場合には、どうしたらよいでしょうか。

この問題を解決するために、インスタンスが1個しか存在しないことを保証するパターンをSingletonパターンと呼びます。



Singletonパターンの登場人物


Singletonパターンは以下のようなクラス構成になっています。

Singleton


Singleton


Singletonパターンには、Singletonクラスしか登場しません。

Singletonクラスの役割はインスタンスが複数個できないようにstatic変数でインスタンスを持ち、getInstanceメソッドでインスタンスを返します。

getInstanceメソッドは、何度呼ばれても同じインスタンスしか返さないという特徴があります。



C++による実装


サンプルとしてSingletonパターンを使用すると一つしかインスタンスを作成しないということを証明するプログラムを作成します。

クラス構成は以下の通りです。


Singleton


Singletonクラス


外部からnewできないようにコンストラクタとデストラクタがpirvateになっているのが特徴です。


ヘッダファイル

class Singleton{
private:
    Singleton();
    ~Singleton();

public:
    static Singleton* getInstance();

private:
    static Singletonm_singleton;
};


ソースファイル

#include <iostream>
#include "Singleton.h"

SingletonSingleton::m_singleton = nullptr;

Singleton::Singleton()
{
    std::cout << "インスタンスを生成しました" << std::endl;
}

Singleton::~Singleton(){
}

Singleton* Singleton::getInstance(){
    if (!m_singleton){
        m_singleton = new Singleton();
    }
    return m_singleton;
}



main関数


上記で作成したSingletonクラスを実際に使用してみます。


ソースファイル

#include <iostream>
#include "Singleton.h"

SingletonSingleton::m_singleton = nullptr;

Singleton::Singleton()
{
    std::cout << "インスタンスを生成しました" << std::endl;
}

Singleton::~Singleton(){
}

Singleton* Singleton::getInstance(){
    if (!m_singleton){
        m_singleton = new Singleton();
    }
    return m_singleton;
}


実行結果

Start
インスタンスを生成しました
obj1とobj2は同じインスタンスです。
End

実行結果からコンストラクタが一度しか呼ばれていないこと、obj1とobj2が同じインスタンスであることが分かりますね。



まとめ


今回は、Singletonパターンを実装していきました。

一つしかインスタンスを作成したいときに便利なクラスです。

ちなみに上記の実装はスレッドセーフではないため、最近のSingletonパターンではインスタンスを作成するCreateメソッドやインスタンスを削除するDestoryメソッドなどを作成してスレッドセーフを保証したりもしています。

スレッドセーフなSingletonパターンを以下の記事で作ってみました。




Singletonパターンは、すごく便利ですがglobal関数的な使い方もできるため試験が行いにくくなるという欠点もあります。

使いどころを考えて使用していきましょう。






3753817_s

C++の実行時間を計測するときにWindowsだとどうしたらいいのか迷うことが多かったので、精度別の実行時間の計測方法をまとめていきます。



clock(10ミリ秒程度)


clockはC言語でも使用できる標準形の関数で、プログラム実行開始からの経過時間をミリ秒単位で返します。

精度は処理系に依存しますが、Windowsでは10ミリ秒程度です。

C言語の標準ライブラリに含まれる関数なのでWindows以外でも使える点が嬉しいです。

以下は、実行時間計測のサンプルプログラムです。

#include <iostream>
#include <vector>
#include <time.h>

using namespace std;

int main(void){    
    clock_t start = clock();

    // 計測対象(今回はvectorに1000000個要素を追加する)
    vector<inttestVec;
    for (int i=0i<1000000; ++i){
        testVec.push_back(i);
    }
    clock_t end = clock();

    cout << end-start << endl;


    return 0;
}



timeGetTime(1ミリ秒)


timeGetTimeはWin32 APIで用意されている関数で、システム起動開始からの経過時間をミリ秒単位で返します。

精度は1ミリ秒程度です。

timeGetTimeを使用するためには、windows.hのインクルードと、winmm.libのリンクが必要です。

以下は、実行時間計測のサンプルプログラムです。

#include <iostream>
#include <vector>
#include <windows.h>

using namespace std;

int main(void){    
    DWORD start = timeGetTime();

    // 計測対象(今回はvectorに1000000個要素を追加する)
    vector<inttestVec;
    for (int i=0i<1000000; ++i){
        testVec.push_back(i);
    }
    DWORD end = timeGetTime();

    cout << end-start << endl;


    return 0;
}






QueryPerformanceCounter(マイクロ秒)


こちらもtimeGetTimeと同様にWin32 APIで用意されている関数です。

精度は環境に依存しますが、マイクロ秒~10ナノ秒程度で、Windowsで使える時間計測の方法で最も精度が高いです。

しかし、実行時間の計測開始前にQueryPerformanceFrequency関数でカウントアップする周波数を取得するなど少し他のAPIに比べて手間がかかるという欠点もあります。

以下は、実行時間計測のサンプルプログラムです。

#include <iostream>
#include <vector>
#include <windows.h>

using namespace std;

int main(void){    
    LARGE_INTEGER frequency;
    QueryPerformanceFrequency(&frequency);

    LARGE_INTEGER start;
    QueryPerformanceCounter(&start);

    // 計測対象(今回はvectorに1000000個要素を追加する)
    vector<inttestVec;
    for (int i=0i<1000000; ++i){
        testVec.push_back(i);
    }
    
    LARGE_INTEGER end;
    QueryPerformanceCounter(&end);

    cout << (double)(end.QuadPart - start.QuadPart) / frequency.QuadPart << endl

    return 0;
}



まとめ


今回はWindows環境でC++の実行時間を計測する方法をまとめました。

Linuxと違ってナノ秒単位で正確に計測することはできませんが、QueryPerformanceCounterを使えばかなり正確に実行時間が計測できることが分かりました。

他にも良い計測方法があればコメントで教えていただけますと幸いです。



3753817_s


C++でvectorに対してループを行う方法が色々あります。

今回は、どの書き方が一番効率が良いのかが気になったので調べてみました。




カウンタ方式


カウンタ変数を用意して、データにアクセスする方式。

今回時間の計測は、windows APIのQueryPerformanceCounterを使用して計測します。

「計測対象」とコメントで書かれている部分のループが実行時間の計測対象になります。
カウンタ方式以外では計測対象以外の部分のコードは省略します。

#include <iostream>
#include <vector>
#include <windows.h>

#define LOOPNUM 1000000
using namespace std;

int main(void){
    vector<inttestVec;
    for(int i=0i<LOOPNUM; ++i){
        testVec.push_back(i);
    }

    LARGE_INTEGER frequency;
    QueryPerformanceFrequency(&frequency);

    LARGE_INTEGER start;
    QueryPerformanceCounter(&start);

    // 計測対象
    for(int i=0i<testVec.size(); ++i){
        int a = testVec[i];
    }

    LARGE_INTEGER end;
    QueryPerformanceCounter(&end);

    DWORD end_tm = timeGetTime();
    std::cout << (double)(end.QuadPart - start.QuadPart) / frequency.QuadPart * 1000000 << std::endl

    return 0;
}



イテレータ方式


C++のコンテナクラスのイテレータを使用してデータにアクセスする方式。

for(auto itr=testVec.begin(); itr!=testVec.end(); ++itr){
    int a = *itr;
};



範囲ベース方式


範囲ベース方式は、C++ 11で追加されたコンテナオブジェクトの範囲分ループを行うという方式。

for(intvec : testVec){
    int a = vec;
}



for_each


for_eachは範囲内の全てのイテレータに特定の関数を適用させるときに使用する方法。

void substitute(int x){
    int a = x;
}

for_each(testVec.begin(), testVec.end(), substitute);






実行時間計測結果


今回は、最適化なしと最適化ありでそれぞれ10回ずつ計測して、平均値を実行時間とします。

vectorのサイズは1000000、実行時間の単位はμs(マイクロ秒)です。

実行時間(μs)
カウンタ方式2400.42
イテレータ方式7285.76
範囲ベース方式5402.42
for_each66284.8


最適化なしだとカウンタ方式が最速ということになりました。

余談になりますが、最適化オプション最高(-Ofast オプション)でコンパイルして、実行してみたところ全ての実行時間が0.1μs未満になり正確に計測できませんでした。

最適化オプション込みで実行時間を比較するにはどうすればよかったのだろうか…。



まとめ


今回はvectorのループの実行時間を計測していきました。

最適化オプションなしでの計測になってしまいましたが、Debug環境ではカウンタ方式が最速みたいです。

正直イテレータとかの方が早いのかなと思っていたので意外な結果になりました。



↑このページのトップヘ