macbookpro(late 2016) 15inchのホコリ取り

去年から夏が近くなるとmacの挙動がおかしくなるということが増えた。かなり発熱していたのでとりあえず小さな卓上扇風機やらファン側を持ち上げておくための小さなスタンドを置いてみたりなどして、一旦は落ち着いたのでそのまま使っていた。

ちなみに小さなスタンドはこちら。シンプルだがそれなりに高さもあって使いやすく、PCが滑ってしまうようなこともなかった。

今年もまた夏が近くなって暑くなってきたところ、去年に比べてcpuが暴走する回数が段違いに増えた。

症状としてはload averageが100を超える状態が10分くらい続く感じで、重そうな各プロセスを落として置いておくとそのうち収まる。ただ、なかなか収まらずにPCの再起動をかけたことも何度かある。

アクティビティモニタを見ると暴走時にはkernel_taskが100%~500%くらいになっていることがわかった。ググるとこういうのが出てくる。
https://blog.kabocy.com/repair-custom/3710/#How-to-check-the-runaway-of-kernel_task

cpuを使っていると偽装することで、実際に重いプロセスがcpuを使いすぎないようにしてcpu温度を下げる、ということをしているとのこと。

上記のページに書いてあるようにSMCリセットなども試したが、しばらくするとやはり同じ状態になってしまうため、そもそも排熱がうまくいってないと考えられる。

実際のところ、2016年後半にPCを買ってから一度も裏蓋を開けての掃除などやっていないため、どう考えてもファンにホコリが溜まっていると思われる。なので今年はちゃんと掃除することにした。

裏蓋の開け方はネジを外すだけではダメで、ちょっと力の加え方に工夫が必要で手間取った。この動画が最もわかりやすかった。
https://eshop.macsales.com/blog/40551-rocket-yard-video-get-inside-a-2016-macbook-pro-simply-and-safely

あとは写真付きの手順としてこちらなど
http://www.macotakara.jp/blog/macintosh/entry-31274.html
https://hotpcb.sakura.ne.jp/log/archives/3455

星型のドライバーは以前何かのために買って持っていたのだが、取っ手付きの吸盤は見つからなかったため以下の2つで代用した。

  • ガムテープ(布)
  • メモ帳の1ページ

ネジを外した後、手前側を少し持ち上げた状態にして手前方向にスライドさせるように力を加える必要があるのだが、指を引っ掛ける部分がないので道具を使わないとうまく力をかけることができない。なので

  • 手間側の少し隙間の空いた部分に5つ折りくらいにしたメモ帳を挟みこんでおく
  • ガムテープを長めに切って吸着した面積を大きくして貼り付け、余った部分を引っ張る

とすることで、無事開けることができた。

開けた直後のファン部分の写真がこちら

中央の円形がファンだが、その円周が白くなっているのはすべてホコリである。ホコリを拭き取りエアダスターで吹き飛ばすとちゃんと円周部分もその周囲と同じく黒くなる。ちなみに、ファンに向けて断続的にエアダスターを繰り返し吹きかけていたら、上部の隙間から大きめのホコリが出てきた。なのでホコリが見えなくなってもしばらくシュッシュッとやっておいた方がよい。

掃除後にとりあえず1日くらい使ってみたところでは、扇風機を回さない状態でも今までのようなkernel taskは発生しなかった(外の気温は同程度)。

自分でホコリ取りする場合の注意点だが、裏蓋を開けての作業はapple側での保証などなくなる行為とのこと。

私の場合は6年経っていて保証もないし、かつappleシリコンの新しいmacbookproをそろそろ買おうかと考えているくらいでだったので、万一失敗して壊しても仕方ないくらいのつもりでやった。なのでもしどうしても壊れては困るという場合はちゃんと修理に持ち込んだ方がよいかもしれないので注意。

whileでハマってTLE

前回このような記事を書いたのでついでにTLEとなったしまった例をもう一つ備忘録としてメモ。
https://jsapachehtml.hatenablog.com/entry/2022/06/05/163223

以下のコードはMを超える最小の2の指数を求める処理。無限ループに陥る場合があるがどのようなケースか思いつくだろうか?

using ll = long long;

ll M = <問題の入力値>

int m = 0;
while ((1<<m) <= M) ++m;

cout << m << endl;

答えは、(1<<m)がintの最大値を超えた場合。つまりMの値がintの最大値を超えているとき。(1<<m)だとintとして扱われてしまうためオーバーフローして小さい値となってしまう。そのためいつまでも条件式が満たされることはない。

long longとして扱うには以下のように書く必要がある(見やすいよう大文字にしてる)

while ((1LL<<m) <= M) ++m;

これだけしかコードがないので上記の場合はちょっと考えると気づくかもしれないが、他にもいろいろ書いてあると意外と気づけないので注意。

atcoderのabc254_eでTLE

こちらの問題で実行時間制限を超えてしまったのだが、普段あまり意識していなかった部分で引っかかっていたのでメモ。
https://atcoder.jp/contests/abc254/tasks/abc254_e

問題の概要は以下

  • グラフが与えられる(頂点数N=1.5e5)
  • クエリが与えられる(クエリ数Q=1.5e5)
  • クエリで与えられた頂点の近傍kまでの頂点番号の和を取る

気をつける点としては計算量で

  • 対象の頂点の近傍kまでの頂点のみ探索する必要がある
    • 頂点の次数は3以下というのが問題で与えられているため、途中で探索を打ち切れば一回あたりの探索頂点数は100以下で済む
  • なので全体の計算量は 100Nには収まり無事制限時間内に終わる

という形。なので素直に幅優先探索を実装して条件に合わせて探索を打ち切るようにしたのだがそれでもTLEとなってしまった。

コードはこんな感じ。

using Graph = vector<vector<int>>;
using P = pair<int,int>;

ll bfs(const Graph &G, int x, int k) {
    int N = G.size();
    deque<P> que;
    que.push_back({0, x});

    vector<int> dist(N, -1);
    dist[x] = 0;

    ll ret = x+1;
    int count = 0;
    while (!que.empty()) {
        auto &p = que.front();
        que.pop_front();

        int d = p.first;
        int v = p.second;

        // 元の頂点からの距離がk以上になった場合には探索終了
        if (d >= k) break;

        for (auto nv : G[v]) {
            ++count;
            if (dist[nv] != -1) continue;
            dist[nv] = d+1;
            ret += nv+1;
            que.emplace_back(d+1, nv);
        }
    }

    return ret;
}

void _main() {
    int N, M;
    cin >> N >> M;

    Graph G(N);
    REP(i, M) {
        int a, b; cin >> a >> b;
        --a; --b;
        G[a].push_back(b);
        G[b].push_back(a);
    }

    int Q;
    cin >> Q;
    REP(i, Q) {
        int x, k; cin >> x >> k;
        --x;
        cout << bfs(G, x, k) << endl;
    }

}

int main() {
    _main();
    return 0;
}

結局コンテスト終了までにバグは見つけられなかったので、終了後に解説のコードと比較しつつ考えたところ、やはり探索部分についての考え方や実装は問題なさそうだった。
https://atcoder.jp/contests/abc254/editorial/4052

その後も気づくまでに時間がかかったのだが、最終的にどこが問題だったかというとbfsの関数内でdistのvectorを初期化していることだった。

vector<int> dist(N, -1);

ここが普段私が意識していなかった部分だったのだが考えてみれば当然で、メモリ確保とその初期値設定には大きさに応じた処理数が必要になる。今回の問題の場合これを行う関数がクエリ数分呼ばれる形となっていたため、合計で計算量がNQ > 1e10となっていた。

自分にとっての盲点に気づけて大変勉強になりました。