懒惰是人类进步的源动力

  • 苹果的生态系统,有替代品吗——我的“离场成本”

    苹果公司的厉害之处,从大学毕业用到现在,感觉有那么两个点

    1. 恰到好处的软硬件结合,一点不多一点不少
    2. 完善的生态环境,这包括软件生态和硬件生态

    在我这个产品设计的外行来看,这两个是相辅相成的。第1点是保障产品设计出来既能得到消费者欢迎,又能节约成本。产品线的设计(包括同款产品不同的子品牌以及规格配置)都做过严苛的调查,保证最大程度榨取利润。第2点最大化了用户粘性和“离场”成本,并且能逐渐增加用户在苹果产品上的消费水平——例如越来越不够用的iCloud空间。

    而且我越来越觉得设备只是平台,接入各种服务把你绑定得死死的平台。

    鄙人目前的苹果设备以及服务:

    • 手机若干: 主力机,车上导航的机器
    • 平板若干:主力平板,给儿子放歌的平板,给老婆看剧的平板
    • 笔记本一台:非主力,因为好奇macOS买的
    • 手表一块:基本日常携带,主要就是看时间然后算“运动”
    • iCloud:基本上我的照片都在上面
    • Apple Music:一般性使用
    • Airpods: 一直在用

    那么我的“离场成本”是什么,有什么能够替代呢?

    手机:【难度★★★★★】主力机我尝试过换成三星(当时的Note10旗舰)、华为的(当时的P30Pro旗舰)。三星当时的感觉是都中规中矩,拍照其实已经很好了,后来换回苹果的原因是觉得还是有点卡;P30pro当时直接买的顶配(并且老婆大人也有一台),后来替换的原因是觉得屏幕素质太差了。这两台印象中,各用了半年。此外最不能忍的是安卓的通知系统,有人会说你挂个代理然后走FCM推送呀~怎么说呢,还是有一些App会让我很不爽。手机还有一个问题是我的相册,从iCloud导出再导入是一种解法,不过当时耗了我大半天时间先把iCloud照片的原件同步到电脑上然后再传到手机上….

    备用机倒是无所谓,三星的A90 5G也被我拿来当导航机子使过,只不过后来买了SE2没地方用,就放车上了…

    平板:【难度★★★】平板不是我的生产力工具,所以我只要屏幕好+看剧爽+偶尔能连个远程桌面就行了。这点其实我没什么粘性。为什么目前是苹果产品呢?因为懒得挑其他牌子了。

    笔记本:【难度★】这个最好“脱瘾”,因为我的真正生产力是在Windows本上。这个MBA现在是“有空临幸一下”的状态。

    手表【难度★★★】:其实三星也有Galaxy Watch可选,而且看同事的表也挺酷炫的,还有些运动狂人直接用专业的运动表——他们看不起AppleWatch的很,哈哈~ 但怎么说呢,苹果的运动三环,这个设计比较容易让人“上瘾”。我之前为了拿到2020年的全民运动日奖章,晚上8点去西湖骑了一趟…要知道自从小屁孩出生,我已经快半年没有骑行了。

    iCloud【难度★★★★】:我的备忘录,待办事项,日程,照片全在上面。诚然其他厂牌的预装程序里肯定有“从iPhone”转移一项,但是iCloud龌龊的地方就在于他会使绊子让你不能轻松地完全把数据转过去。比如,照片本地存储自动压缩的功能。

    Apple Music【难度★★】:我怀疑Android客户端做那么难用是故意的。另外最近发现Spotify免费又好用,再加上国区的Apple Music有稀奇古怪的缺歌,所以这个粘性不是很大。

    Airpods【难度★★★】:用过其他竞品,这又是一个软硬件结合的产物,有点难戒除

    目前觉得要换设备,只能先从手机开始“脱瘾”。且看今年苹果的新款是否有亮点把,Cook的模具用了那么多代也要换了?

  • ASUS PA90 “迷你”主机 i5 低配版 简单拆机评测

    上周入了一台ASUS PA90迷你主机,由于穷,买的最低配的版本。基础配置如下:

    • i5-9400 (6C6T)
    • 16GB DDR4 2666
    • 256G SSD + 1TB HDD
    • nVIDIA RTX 2060 6GB

    华硕很狡猾,这个型号的机器为了拉开差距,配置再往上选的话有这么两个选项:

    1. 32GB内存 + Quardo P2000显卡,多7000大洋;
    2. i9-9900K + (512G SSD + 1TB HDD),多9000大洋;
      而最低配的价格是9000多大洋。我觉得这个最低配是卖不出去了然后后出的一个子型号。

    拆机

    拆机参考chiphell的评测教程,比较方便地就能拆开了。外面的金属铁壳4枚螺丝,然后里面两侧的保护罩各2枚螺丝就能打开。2.5寸的硬盘口是“挂”在侧面的,一个铁壳子四角有螺丝拧下来,拆起来还算方便。吐槽一些这些金属器件真的是重,大概是为了散热吧。

    ASUS_PA90_0.jpg
    ASUS_PA90_1.jpg
    ASUS_PA90_2.jpg
    ASUS_PA90_3.jpg

    优点

    金属外壳散热比较给力。主板用料还是可以的。机器的运行声音也十分安静,比我以前配的那台带显卡的ITX主机安静多了。
    各种接口很丰富,并且对DIY的支持度还是挺高的,CPU看样子也是可以直接换的(虽然我是没钱换)。

    目前是把1TB的HDD(WD Barracuda叠瓦盘)换成了一个Lexar的1T SSD,运行良好。我又发现这个低配版配的256G SSD居然是金士顿的,而且是SATA3协议你敢信!试了一下把内存跑满,然后就发现机器异常卡,虚拟内存在缓慢地写啊写啊写Orz… 好在这个机子有2个2280的槽分布在主板的两面(主板背面还挺难拆的,好在预装的那一个在主板背面),我的海康威视C2000拼爹爹专攻版已经在路上了,哼!

    坑点

    电源是两块大砖头

    是的你没看错,这货的电源是外置的两块砖头,我收到的一块是230W,另一个是280W。其中280W的电源是专供显卡用的。试了一下,如果拔去显卡的那条线,机子也能开起来哈哈哈。

    挑内存

    这货用的笔记本内存,然后我从ThinkPad上拆下来一条杂牌16G 2666内存插上去,结果机器直接不启动。更坑的是我好像没有听到主板报警声,难道蜂鸣器也被取缔了?
    然后我京东购入了一条DDR4 2666 HyperX金士顿本条,插上后能成功点亮,但是不知为何频率被限制在了2400. 这周没时间了,下周来查查原因看,总之感觉有点坑。

    更新: 杂牌内存不启动的问题得到了解决。主板上原有一条16G的内存条,一共4个DIMM口。在插第二条内存的时候,比如原来插了DIMM0,那只能插DIMM2组双通道,不然会不亮。但是降频的问题还是继续,管他呢,好歹现在能用48G内存了~

    阉割了水冷

    从高配版的评测图可以看到CPU是水冷的。我手上这台,由于用了i5的处理器加上这么便宜,厂房就连水冷都取消掉了。原有水冷的地方用了一块铁板遮羞:(

    显卡是8Pin的电源

    瞄了一眼显卡,是用的8Pin电源。但是又看到预留另外的供电,但是没有线缆插在上面。

  • Microsoft Edge (Chromium内核) 离线安装包下载 84

    普通下载的安装包都是在线安装的,对于企业部署或者其他的离线环境来说不大友好。

    其实这种情况下下载Edge商业版就行,具体方法是进入 https://www.microsoft.com/zh-cn/edge/business/download

    截止本文发文时,最新的版本是84.0.522.52,下载地址在此:
    http://dl.delivery.mp.microsoft.com/filestreamingservice/files/36d8a910-0311-40a1-82f5-68f003f95abb/MicrosoftEdgeEnterpriseX64.msi

  • 使用互信息(Mutual Information)来评价特征

    特征(feature, X)与响应(outcome, y)之间的互信息(mutual information, MI)是一种衡量两个变量之间相关性的方法。该方法将相关性这一定义拓展到非线性的关系上。具体而言,它衡量了一个随机变量经由另一随机变量能得到的信息量。

    MI的概念与信息熵(entropy)的概念密不可分。信息熵度量了一个随机变量携带的信息量。形式上,两个随机变量(X,Y)的互信息I(X,Y)定义如下:
    连续形式
    continuous mutual information

    离散形式
    discrete mutual information

    sklern.feature_selection.mutual_info_regression方法实现了计算所有特征与一个连续输出之间互信息值的函数,可用于挑选最可能携带预测信息的特征。它也提供一个分类器版本。

    本文全文翻译自Stefan Jansen’s Hands-On Machine Learning for Algorithmic Trading*

  • Support parallel XZ decompression for unix (7zip LZMA SDK based, C/C++)

    为unix平台增加XZ多线程解压缩支持(基于7zip LZMA SDK, C/C++)

    Note

    This post has nothing to do with the pixz project. I am talking about decompressing the original xz archive using 7-zip’s LZMA SDK under unix environment.

    Background

    Originally the 7zip’s LZMA SDK (version 19.00) only covers parallel xz decompression for Windows systems. This post shows the C code that adds support for lib pthread, i.e. unix systems.
    Compiling with C++ using the C library should also work, I have tested it on my own box.

    Little Details

    Actually the original writer has completed all the necessary abstraction for the multi-threading pipeline. All I have done is adding some macros and pthread equivalents to Windows threading model.

    Usage

    Replace lzma/C/... with below files. The new code should work on both Windows and Unix systems.

    Git Repo

    See https://github.com/idailylife/lzma-xz-parallel

    Source Code

    Threads.h

    /* Threads.h -- multithreading library
    2017-06-18 : Igor Pavlov : Public domain */
    
    #ifndef __7Z_THREADS_H
    #define __7Z_THREADS_H
    
    #ifdef _WIN32
    #include <windows.h>
    #else
    #include <pthread.h>
    #endif
    
    #include "7zTypes.h"
    
    EXTERN_C_BEGIN
    
    WRes HandlePtr_Close(HANDLE *h);
    WRes Handle_WaitObject(HANDLE h);
    
    #ifdef _WIN32
    typedef HANDLE CThread;
    #define Thread_Construct(p) *(p) = NULL
    #define Thread_WasCreated(p) (*(p) != NULL)
    #define Thread_Close(p) HandlePtr_Close(p)
    #define Thread_Wait(p) Handle_WaitObject(*(p))
    #else
    typedef void* LPVOID;
    typedef pthread_t* CThread;
    #define Thread_Construct(p) *(p) = NULL
    #define Thread_WasCreated(p) (*(p) != NULL)
    #define Thread_Close(p) HandleThread_Close(*(p))
    #define Thread_Wait(p) HandleThread_Join(*(p))
    WRes HandleThread_Close(pthread_t* th);
    WRes HandleThread_Join(pthread_t* th);
    
    #endif
    
    
    typedef
    #ifdef UNDER_CE
      DWORD
    #else
      unsigned
    #endif
      THREAD_FUNC_RET_TYPE;
    
    #define THREAD_FUNC_CALL_TYPE MY_STD_CALL
    #define THREAD_FUNC_DECL THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE
    typedef THREAD_FUNC_RET_TYPE (THREAD_FUNC_CALL_TYPE * THREAD_FUNC_TYPE)(void *);
    WRes Thread_Create(CThread *p, THREAD_FUNC_TYPE func, LPVOID param);
    
    #ifdef _WIN32
    
    typedef HANDLE CEvent;
    typedef CEvent CAutoResetEvent;
    typedef CEvent CManualResetEvent;
    #define Event_Construct(p) *(p) = NULL
    #define Event_IsCreated(p) (*(p) != NULL)
    #define Event_Close(p) HandlePtr_Close(p)
    #define Event_Wait(p) Handle_WaitObject(*(p))
    WRes Event_Set(CEvent *p);
    WRes Event_Reset(CEvent *p);
    WRes ManualResetEvent_Create(CManualResetEvent *p, int signaled); // not used
    WRes ManualResetEvent_CreateNotSignaled(CManualResetEvent *p); // not used
    WRes AutoResetEvent_Create(CAutoResetEvent *p, int signaled);
    WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p);
    
    #else
    typedef struct {
      bool state;
      pthread_mutex_t mutex;
      pthread_cond_t cond;
    } event_t;
    
    typedef event_t* CEvent;
    typedef CEvent CAutoResetEvent;
    #define Event_Construct(p) *(p) = NULL
    #define Event_IsCreated(p) (*(p) != NULL)
    
    WRes Event_Close(CEvent* p);
    WRes Event_Set(CEvent *p);
    WRes Event_Reset(CEvent *p);
    WRes Event_Wait(CEvent* p);
    WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent* p);
    
    #endif
    
    // CSemaphore is not used for decoding
    #ifdef _WIN32
    typedef HANDLE CSemaphore;
    #define Semaphore_Construct(p) *(p) = NULL
    #define Semaphore_IsCreated(p) (*(p) != NULL)
    #define Semaphore_Close(p) HandlePtr_Close(p)
    #define Semaphore_Wait(p) Handle_WaitObject(*(p))
    WRes Semaphore_Create(CSemaphore *p, UInt32 initCount, UInt32 maxCount);
    WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 num);
    WRes Semaphore_Release1(CSemaphore *p);
    #endif
    
    #ifdef _WIN32
    
    typedef CRITICAL_SECTION CCriticalSection;
    WRes CriticalSection_Init(CCriticalSection *p);
    #define CriticalSection_Delete(p) DeleteCriticalSection(p)
    #define CriticalSection_Enter(p) EnterCriticalSection(p)
    #define CriticalSection_Leave(p) LeaveCriticalSection(p)
    
    #else
    /// use mutex instead
    typedef pthread_mutex_t* CCriticalSection
    WRes CriticalSection_Init(CCriticalSection *p);
    WRes CriticalSection_Delete(CCriticalSection *p);
    WRes CriticalSection_Enter(CCriticalSection *p);
    WRes CriticalSection_Leave(CCriticalSection *p);
    
    #endif
    EXTERN_C_END
    
    #endif
    

    Threads.c

    /* Threads.c -- multithreading library
    2017-06-26 : Igor Pavlov : Public domain */
    
    #include "Precomp.h"
    
    #ifdef _WIN32
      #ifndef UNDER_CE
      #include <process.h>
      #endif
    #endif
    
    #include "Threads.h"
    
    #ifdef _WIN32
    static WRes GetError()
    {
      DWORD res = GetLastError();
      return res ? (WRes)res : 1;
    }
    
    static WRes HandleToWRes(HANDLE h) { return (h != NULL) ? 0 : GetError(); }
    static WRes BOOLToWRes(BOOL v) { return v ? 0 : GetError(); }
    
    WRes HandlePtr_Close(HANDLE *p)
    {
      if (*p != NULL)
      {
        if (!CloseHandle(*p))
          return GetError();
        *p = NULL;
      }
      return 0;
    }
    
    WRes Handle_WaitObject(HANDLE h) { return (WRes)WaitForSingleObject(h, INFINITE); }
    
    #else
    /// unix specific functions
    
    WRes HandleThread_Close(pthread_t* th) {
      free(th);
      th = NULL;
      return 0;
    }
    
    WRes HandleThread_Join(pthread_t* th) {
      return pthread_join(*th, NULL);
    }
    
    #endif
    
    
    #ifdef _WIN32
    WRes Thread_Create(CThread *p, THREAD_FUNC_TYPE func, LPVOID param)
    {
      /* Windows Me/98/95: threadId parameter may not be NULL in _beginthreadex/CreateThread functions */
      
      #ifdef UNDER_CE
      
      DWORD threadId;
      *p = CreateThread(0, 0, func, param, 0, &threadId);
    
      #else
    
      unsigned threadId;
      *p = (HANDLE)_beginthreadex(NULL, 0, func, param, 0, &threadId);
       
      #endif
    
      /* maybe we must use errno here, but probably GetLastError() is also OK. */
      return HandleToWRes(*p);
    }
    #else
    pthread_attr_t g_th_attrs[64]; //NOTE: maximum of 64 threads
    size_t g_th_index = 0;
    
    WRes Thread_Create(CThread *p, THREAD_FUNC_TYPE func, LPVOID param)
    {
      *p = malloc(sizeof(pthread_t));
      pthread_t* th = *p;
      int ret = pthread_attr_init(&(g_th_attrs[g_th_index]));
      assert(ret==0);
      ret = pthread_create(th, &(g_th_attrs[g_th_index]), func, param);
      g_th_index++;
      return ret;
    }
    #endif
    
    
    #ifdef _WIN32
    static WRes Event_Create(CEvent *p, BOOL manualReset, int signaled)
    {
      *p = CreateEvent(NULL, manualReset, (signaled ? TRUE : FALSE), NULL);
      return HandleToWRes(*p);
    }
    
    WRes Event_Set(CEvent *p) { return BOOLToWRes(SetEvent(*p)); }
    WRes Event_Reset(CEvent *p) { return BOOLToWRes(ResetEvent(*p)); }
    
    WRes ManualResetEvent_Create(CManualResetEvent *p, int signaled) { return Event_Create(p, TRUE, signaled); }
    WRes AutoResetEvent_Create(CAutoResetEvent *p, int signaled) { return Event_Create(p, FALSE, signaled); }
    WRes ManualResetEvent_CreateNotSignaled(CManualResetEvent *p) { return ManualResetEvent_Create(p, 0); }
    WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p) { return AutoResetEvent_Create(p, 0); }
    
    #else 
    ///unix
    
    WRes Event_Close(CEvent* p) {
      if (!p || !(*p))
        return 0;
      event_t* evt = *p;
      pthread_cond_destroy(&evt->cond);
      pthread_mutex_destroy(&evt->mutex);
      free(evt);
      *p = NULL;
    }
    
    
    WRes Event_Set(CEvent *p) {
      event_t* evt = *p;
      if (pthread_mutex_lock(&evt->mutex) != 0) {
        return 1;
      }
      evt->state = true;
    
      if (evt->manual_reset) {
        if (pthread_cond_broadcast(&evt->cond)) {
          return 1;
        }
      } else {
        if (pthread_cond_signal(&evn->cond)) {
          return 1;
        }
      }
    
      if (pthread_mutex_unlock(&evt->mutex)) {
        return 1;
      }
    
      return 0;
    }
    
    WRes Event_Reset(CEvent* p) {
      event_t* evt = *p;
      if (pthread_mutex_lock(&evt->mutex)) {
        return 1;
      }
    
      evt->state = false;
    
      if (pthread_mutex_unlock(&evt->mutex)) {
        return 1;
      }
    
      return 0;
    }
    
    WRes Event_Wait(CEvent* p) {
      event_t* evt = *p;
      if (pthread_mutex_lock(&evt->mutex)) {
        return 1;
      }
    
      while (!evt->state) {
        if (pthread_cond_wait(&evt->cond, &evt->mutex)) {
          pthread_mutex_unlock(&evt->mutex);
          return 1;
        }
      }
    
      evt->state = false;
      if (pthread_mutex_unlock(&evt->mutex)) {
        return 1;
      }
      return 0;
    }
    
    WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p) {
      *p = malloc(sizeof(event_t));
      memset(evt, 0, sizeof(event_t));
      evt->state = false;
      evt->manual_reset = false;
      if (pthread_mutex_init(&evt->mutex, NULL)) {
        return 1;
      }
      if (pthread_cond_init(&evt->cond, NULL)) {
        return 1;
      }
      return 0;
    }
    
    #endif
    
    
    #ifdef _WIN32
    
    WRes Semaphore_Create(CSemaphore *p, UInt32 initCount, UInt32 maxCount)
    {
      *p = CreateSemaphore(NULL, (LONG)initCount, (LONG)maxCount, NULL);
      return HandleToWRes(*p);
    }
    
    static WRes Semaphore_Release(CSemaphore *p, LONG releaseCount, LONG *previousCount)
      { return BOOLToWRes(ReleaseSemaphore(*p, releaseCount, previousCount)); }
    WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 num)
      { return Semaphore_Release(p, (LONG)num, NULL); }
    WRes Semaphore_Release1(CSemaphore *p) { return Semaphore_ReleaseN(p, 1); }
    
    #endif
    
    
    #ifdef _WIN32
    WRes CriticalSection_Init(CCriticalSection *p)
    {
      /* InitializeCriticalSection can raise only STATUS_NO_MEMORY exception */
      #ifdef _MSC_VER
      __try
      #endif
      {
        InitializeCriticalSection(p);
        /* InitializeCriticalSectionAndSpinCount(p, 0); */
      }
      #ifdef _MSC_VER
      __except (EXCEPTION_EXECUTE_HANDLER) { return 1; }
      #endif
      return 0;
    }
    
    #else
    WRes CriticalSection_Init(CCriticalSection *p) {
      *p = malloc(sizeof(pthread_mutex_t));
      if (pthread_mutex_init(*p, NULL)) {
        return 1;
      }
      return 0;
    }
    
    WRes CriticalSection_Delete(CCriticalSection *p) {
      pthread_mutex_t* mtx = *p;
      return pthread_mutex_destroy(mtx);
    }
    
    WRes CriticalSection_Enter(CCriticalSection *p) {
      if (pthread_mutex_lock(&evt->mutex)) {
        return 1;
      }
      return 0;
    }
    
    WRes CriticalSection_Leave(CCriticalSection *p) {
      if (pthread_mutex_unlock(&evt->mutex)) {
        return 1;
      }
      return 0;
    }
    
    #endif
    

    XzDec.c

    add the following macro

    #ifndef _WIN32
    #define S_OK 0x00000000
    #define E_FAIL 0x80004005
    #endif

    MtDec.c

    replace function ThreadFUnc1 with:

    static THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE ThreadFunc1(void* pp)
    {
      static int g_ok_stat = 0x0;
      static int g_err_stat = 0x80004005;
      WRes res;
      CMtDecThread *t = (CMtDecThread*)pp;
      CMtDec *p;
    
      res = ThreadFunc2(t);
      p = t->mtDec;
      if (res == 0) {
    #ifdef _WIN32
        return p->exitThreadWRes;
    #else
        if (p->exitThreadWRes) { return &g_err_stat; }
        else { return &g_ok_stat; }
    #endif
      }
      {
        // it's unexpected situation for some threading function error
        if (p->exitThreadWRes == 0)
          p->exitThreadWRes = res;
        PRF(printf("\nthread exit error = %d\n", res));
        p->exitThread = True;
        Event_Set(&p->threads[0].canRead);
        Event_Set(&p->threads[0].canWrite);
        MtProgress_SetError(&p->mtProgress, MY_SRes_HRESULT_FROM_WRes(res));
      }
    #ifdef _WIN32
        return res;
    #else
        return &g_err_stat;
    #endif
    }
  • 主动投资管理扫盲——什么是IC, IR, 夏普率…

    初涉主动投资管理(以及量化)的小同学可能会被一些缩写搞烦。下面是其中几个入门级的指标,他们常被用来评价某个策略的绩效(performance)。

    夏普率 Sharpe Ratio

    夏普比率(SR)将投资组合的预期超额投资组合与该超额收益的波动率进行比较,该波动率以其标准偏差衡量。 它以每单位风险的平均超额收益来衡量补偿:
    SR = (R_a - R_f) / σ_a

    其中:

    • R_a: 投资组合的收益率,这本来是一个期望值(expectation),但是通常会用投资组合至今N期的平均收益率作为观测值估计它;
    • R_f: 无风险利率
    • σ_a: 投资组合收益的标准差,也是一个期望值,这边通常用至今N期投资组合收益率的标准差来估计它;

    信息系数 Information Coefficient(IC)

    IC会测量Alpha因子与信号产生的前瞻收益之间的相关性,并捕获经理预测技能的准确性。投资组合的截面IC通常是指模型预测的股票下期收益率(i.e.某alpha因子在这一时刻T给所有股票的因子值)与股票下期(T+x)实际的收益率的相关性。假设有3000只股票,那相关性就是两个3000维向量的相关系数,多半用皮尔森相关系数计算。

    信息比率 Information Ratio(IR)

    基于IC的一个统计量:IR = mean(IC) / std(IC) 均值除以方差。较高的信息比率(IR)意味着相对于所承担的单位风险下拥有更高的信息系数。

  • 内地可用的漫游数据卡对比介绍 CMHK, CUniq

    本篇文章主要做一个横向对比,对比对象是各种内地可用用来上网的数据漫游卡以及它的网速和资费。

    参加对比的卡种类及其链接

    Specifications

    unique-ptr 鸭寮佳 Cuniq 3HK CMLink
    本土运营商 CMHK 中国联通(香港) 3HK和记 CMLink Global
    漫游运营商 中国移动 中国联通 中国联通 中国移动
    费用(官方报价) HK$218 HK$168 HK$198 CN¥210
    TB报价 CNY171 CNY145 CNY168 N/A
    流量/GB 10 10 5通用 + 5社交专用 无限
    限速 N/A N/A N/A 每日500MB不限速,随后8Mbps
    计费周期 365日 180日 365日 30日
    续费 可,數據用量達上限後,數據服務將會自動終止,客戶如需繼續使用數據服務,客戶需先取消本來組合,並重新申請自選數據組合 可,每增值HK$200可購買額外10GB大中華數據用量,有效期180日 可续 可,按月续费
  • 居然有5G网络! CMLink Global 留学生网课卡(7元/天不限量) 购买及简单评测

    概述

    商品链接: https://global.cmlink.com/store/item?id=I_200403023958_2165
    套餐:大中华30天(留学生网课卡)

    • 30天不限流量
    • 套餐天数从激活起每24小时为1天开始计算
    • 套餐必须在购买后180天内激活使用
    • 可寄大陆:可
    • 内地PRL网络: 中国移动
    • 可否续费:可
    • 合约:无合约
    • 备注:不支持发送短信,不支持接打电话(纯流量卡)

    购买

    一些个人博客提到这张卡需要非大陆地区手机号购买,实则不然。笔者尝试直接在官网(上面这个链接)下单,能够成功购买并使用套餐。唯一的需求就是要使用Master/Visa信用卡支付,但是信用卡并不限制地区。笔者的招商银行Visa卡成功付款。
    中间出现了些小插曲,我能收到订购成功短信但是迟迟没有发货。与客服cs@cmlink.com联系后反馈手机号码填错(少了第二、第三位,变成了美国号码).然而我的手机号确实收到了订购成功的短信。这个可能是系统故障,也可能是个例,不过只要你有订单号,客服的反应速度还是挺快的。最后还是用顺丰加急帮我发卡了。
    cmlink_global.jpg

    测速

    目前看来应该是每天头500MB不限速的,据说限速之后也能维持8Mbps的速度,一般使用其实无问题。使用一周下来,每天超过500MB用量的时候也没有收到什么提示短信,目前还没有体验到限速效果。
    5G网络连接今天到城区里试了一下,确实可以连上5G信号,使用Samsung Galaxy A90在屋内测速结果如下:
    cmlink_hk_5G.jpg

    总结

    如果不考虑价格因素的话(其实不限流量的政策还挺良心的),这是一张好卡。

  • Visual Studio Code 如何在离线环境下安装远程调试(vscode-server)以及安装各种插件

    公司的生产环境里的服务器是禁止了绝大多数互联网连接的,但是有些环境又只有生产环境里有。为了方便,以前总是直接ssh进去用vim写代码,但是总觉得效率还是不够高。(这是我的问题😄)。另一个方法是在本地机器上写好代码,然后rsync过去一把梭。但是写一些稍大的工程的时候,一把梭很难成功。今天决定趁周末解决这种“非技术问题”。

    远程搬砖码字调试的意思如下图:
    vscode-remote-dbg-arch

    画个重点就是:

    • 本地机器上安装好VS Code;
    • 远程机器上安装好调试工具(vscode-server);
    • 代码留在远程机器上,本地机器仅作为”文本编辑器”这样的前端界面;

    本地机器安装远程调试插件(Remote Development Extension)

    这个步骤没什么难度,因为本地机器是可以连上网的。在VS Code的Extension页面搜索”Remote Development”安装便是。
    这边提一句,笔者的环境是“墙中墙”,运行VS Code的也是一个“虚拟机”,里面也没网。像这样的情况,需要根据远程连接的类型(SSH, WSL, Docker…)选择具体的插件,比如SSH的话去 https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh 这个页面,点击右侧的”Download Extension”下载一个vsix后缀的文件传到墙里面。然后选择下图”…”里面的”Install from VSIX”离线安装

    install_from_vsix.PNG

    离线安装vscode-server调试服务器

    正常安装完远程调试插件以后,左侧边栏会多出一个远程连接的tab. 创建一个连接以后,右键那个连接选择”connect to host using new window”会新建一个窗口连接远程服务器。右下角会弹出一个泡泡提示正在安装什么的,这时候要点击”details”看一下详情。这个步骤因为没有网络,它是永远无法完成的…除非我们人为帮他下载好了放好

    install_vs_debug_server.PNG
    留意上图红框中的commit hash,我们需要从微软官网手工把这个东西下载下来。

    找个有网的机器,去下载https://update.code.visualstudio.com/commit:${commit_id}/server-linux-x64/stable,地址里面的${commit_id}用刚才红框里的代替。下载下来应该是个tar文件,然后把它传到你的远程机器上一个临时位置,这边存放的文件夹用/tmp代替.
    随后操作:

    1. 创建vscode-server的文件夹
    2. 解压缩
    3. touch一个0文件,表示加载已经全部完成
      具体bash如下:
    mkdir -p ~/.vscode-server/bin/${commit_id}
    # assume that you upload vscode-server-linux-x64.tar.gz to /tmp dir
    tar zxvf /tmp/vscode-server-linux-x64.tar.gz -C ~/.vscode-server/bin/${commit_id} --strip 1
    touch ~/.vscode-server/bin/${commit_id}/0

    搞定之后,重新执行刚才的连接远程服务器的操作。这时候如果去看details会发现VS Code认为服务器文件已经下载好了,于是他就会开始各种部署。

    离线安装扩展插件到远程服务器

    成功连接后你会发现一些本地装好的插件在远程是没有的。比如下图中的C++插件就是处于本地有但是远程没有的状态。这种插件一般都是需要获取编译运行环境的,由于本地无法模拟远程环境,所以必须要在远程装对应插件。

    remote_ext_vscode.PNG

    离线安装远程插件其实不难。将上文提到的VSIX离线文件下载了传到远程服务器上后(注意vsix有些是区分平台的,要和远程服务器平台一致,而非本地),可以直接安装。

  • DELL XPS 13 9360 电源无法识别 BIOS报错 维修手记 (附官方拆机手册)

    序:老婆大人的XPS13突然坏了,体现在电源口歪斜、转轴部分螺丝松动。拆机后发现是C面的问题,具体从D面看是右上角螺丝的底座柱(类似螺母)碎裂了。无奈花了300块淘宝了一个C壳拆了老半天换上。装好之后一切看起来都很完美,但是插上原装电源开机后BIOS就报错了,提示类似下图:
    dell_ac_adapter_error.jpg(source: https://www.dell.com/community/Inspiron/AC-Power-Adapter-Alert-Inspiron-5558/td-p/6109628)

    意思是电源适配器无法识别,电脑无法充电。这个时候选择Continue是可以进操作系统的,但是系统也会显示电源连接但是不在充电的状态。

    一开始以为是适配器哪里崩了,后来一想这种原装电源挂掉概率实在是太小了,怀疑是电源适配器接口的故障。淘宝又花了十几块买了个电源头(型号00P7G3)换上,发现恢复了正常。拆下来的电源头仔细揣摩了一下,似乎是拧螺丝的时候把一根线压到了(但是没看见断),这个大概跟功率识别有关系吧!

    dell_ac_power_socket.jpg

    btw, 官网上找到了中文的拆机手册。发现比联想的黑白HMM写的好一点,顺道分享了~

    xps-13-9360-laptop_service-manual_zh-cn.pdf
    链接:https://pan.baidu.com/s/1Q8KYxEB-Vi6JyPvzFS659w
    提取码:cxrk

    后记:修好了之后,老婆大人并没有表示特别开心,反而说什么“键盘比以前难摁了”之类的。看来是想用新电脑了吧Orz.