Dec 4

PHP的除法 不指定

felix021 @ 2015-12-4 20:49 [杂碎] 评论(2) , 引用(0) , 阅读(1945) | Via 本站原创
今天要实现一个简单的功能:按需给出长度为n的空格串。

如果是用Python,我就直接用这一句来解决了,既简单又直观:
' ' * n

可惜PHP就是不好使,只好写一个函数。最简单的办法当然是一个循环,但是看起来太蠢了,效率也低(虽然可以用一个数组缓存来解决),看起来也不帅。所以试着写了一个递归的版本(其实也应该要用数组来缓存提高效率,但是以下代码省略了相关逻辑,看起来清晰一点):
function spaces($n)
{
    if ($n == 1)
        return ' ';
    return spaces($n / 2) . spaces($n - $n / 2);
}

看起来酷多了对不对?

不对……因为一调用就递归到死啊魂淡。什么破玩意,连这种代码也不能一次跑对,简直让我对自己的能力产生了怀疑。

好吧,那就调试一下。
function spaces($n)
{
    if ($n == 1)
        return ' ';
    printf("%d = %d + %d", $n, $n / 2, $n - $n / 2);
    fgetc(STDIN);
    return spaces($n / 2) . spaces($n - $n / 2);
}


printf竟然输出了"3 = 1 + 1",这尼玛我都要开始怀疑人生了,什么鬼!

经过各种折腾,最后不知为什么放弃治疗,用了大概这样的代码(说真的,我在运行之前都觉得这是徒劳):
function spaces($n)
{
    if ($n == 1)
        return ' ';
    var_dump($n / 2); var_dump($n - $n / 2);
    fgetc(STDIN);
    return spaces($n / 2) . spaces($n - $n / 2);
}


输出:
引用
float(1.5)
float(1.5)


卧槽……

卧槽……

卧槽……

重说三。

当时已经接近崩溃了,于是到Google去试图寻找一点安慰,“php int division get float”返回给我的第一条,竟然是一个StackoverFlow的问题:How do I get a float value when dividing two integers? (PHP),提问者表示他用 12 / 13 得到的结果是 1 。SF的大神们纷纷出招,甚至还有人回答:
引用
Just use $value = (float)($x/$y);

我看着左边的-1,深藏功与名。

然后继续解决问题。我想知道为什么我这儿两个整数一除就变成了浮点数,然而试了几个不同的环境,都是一样的,看起来倒了这血霉的似乎不是我一个。

查来查去也没查出什么来,于是我鼓起勇气、抛弃羞耻心,找到了一个我觉得我这辈子都不会需要打开的页面:

PHP:算术运算符 - http://php.net/manual/zh/language.operators.arithmetic.php

这个页面标题里的第一句话是“还记得学校里学到的基本数学知识吗?就和它们一样。”。

接下来是一个表格,列出了负号、加、减、乘、除、取余的说明。

表格后面还跟了一句话:

除法运算符总是返回浮点数。

除法运算符总是返回浮点数。

除法运算符总是返回浮点数。

尼玛……这说的学校居然TMD是小学啊……我了个大槽……谁家小学还教 “% 取余” 符号的!!谁家小学教的除法返回浮点数还带余数的!!

然而我还是不太相信,我用了八九年的PHP,我竟然不知道它没有整数除法?

翻,翻源码。

在 php5-src/Zend/zend_vm_def.h 可以看到 除法操作符(ZEND_DIV )的Handler,调用的是 fast_div_function ,这个函数里面有一段被注释掉的老的实现,目前实际调用的是 div_function ,实际上都是一样的,截取后者的一部分:
ZEND_API int div_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */
{
    zval op1_copy, op2_copy;
    int converted = 0;

    while (1) {
        switch (TYPE_PAIR(Z_TYPE_P(op1), Z_TYPE_P(op2))) {
            case TYPE_PAIR(IS_LONG, IS_LONG):
                if (Z_LVAL_P(op2) == 0) {
                    zend_error(E_WARNING, "Division by zero");
                    ZVAL_BOOL(result, 0);
                    return FAILURE;        /* division by zero */
                } else if (Z_LVAL_P(op2) == -1 && Z_LVAL_P(op1) == LONG_MIN) {
                    /* Prevent overflow error/crash */
                    ZVAL_DOUBLE(result, (double) LONG_MIN / -1);
                    return SUCCESS;
                }
                if (Z_LVAL_P(op1) % Z_LVAL_P(op2) == 0) { /* integer */
                    ZVAL_LONG(result, Z_LVAL_P(op1) / Z_LVAL_P(op2));
                } else {
                    ZVAL_DOUBLE(result, ((double) Z_LVAL_P(op1)) / Z_LVAL_P(op2));
                }
                return SUCCESS;
    ....


真的什么都不用说了。

EDIT UPDATE: 那个stackoverflow的问题,其实有人在问题的评论里指出了结果本来就是浮点数,不知道提问者怎么脑抽的。

EDIT UPDATE 2: PHP 7新增了一个 intdiv 函数,还真是非常PHP-Style的函数名和解决方法,看着就令人蛋疼。
Nov 21

免费的代价 不指定

felix021 @ 2015-11-21 21:24 [杂碎] 评论(0) , 引用(0) , 阅读(1494) | Via 本站原创
手头一个T03和Dell Venue 8 Pro,用的都是Intel BayTrail架构1.33GHz(Turbo 1.8GHz)级别的处理器(Z3735F/Z3740D),日常使用问题不大,但是大型任务比较困难,印象中MS Office比较臃肿沉重,所以给装了看起来好像很轻巧的(而且还是免费的)WPS Office。

但是发现这货这几年变化太大了,集成了所谓的“云服务”,以及各种广告、新闻插件,烦不胜烦,于是把wps的wtoolex目录(内含updatesef, wpsupdate, wpsnotify, desktoptips等无良exe)给删掉换成一个只有System用户有权限的空文件,消停了一段时间。

但是这还没完,今天突然发现桌面上出现了一个“爱淘宝”图标,链到

C:\Users\felix021\AppData\Roaming\software\ntatb\AlimamaAgent.exe

ntatb目录下还有个index.html,里面写着一句
引用
meta name="author" content="kingsoft-WPS"/


想起早前看到的一句话:“如果商家为你提供免费服务,那你就不是他们的客户,而是他们的产品"。

简而言之就是,你被卖了。

知乎有一个问题讨论得很有意思,推荐阅读:

为什么说「免费的其实是最贵的」? http://www.zhihu.com/question/22084816
Oct 29

yii framework cache 深坑 不指定

felix021 @ 2015-10-29 18:51 [杂碎] 评论(2) , 引用(0) , 阅读(1932) | Via 本站原创
用框架的cache时发现,command和controller设置的cache互相之间居然是独立的,对于CFileCache,通过grep可以在 app/runtime/cache 下面看到,相同的key在两边set以后,会存在两个不同的文件里面:显然是在hash得到cache key的时候用了一个额外的串。

没办法,只能去翻Yii的源码,在 framework/cache/CCache.php 可以看到get/set方法是调用了 generateUniqueKey 来生成cache key;该方法用到的keyPrefix 是取值于 "Yii::app()->getId()" 。追过去,在 framework/base/CApplication.php 里面可以看到,这个getId()的实现是"crc32($this->getBasePath().$this->name)"。

于是去看了一下,果然 config 下面 main.php 和 console.php 配置的 name 不同,所以框架把它们认为是两个project。改成一样,就解决了。

但是……

还有一个隐藏得更深的坑:

缓存文件(例如runtime/cache/04/51/045113ce863440795dab03b29a4ec384.bin)里的内容是这样的:
引用
a:2:{i:0;s:26:"2015-10-29 18:28:03command";i:1;N;}


也就是说,这个缓存在遇到两个md5相同的key时,无法做出区分!

这个只能看命了。

UPDATE:
缓存冲突问题,虽然不能彻底解决,但是可以通过简单的包装,把原先set的value换成 {'key': ..., 'value': value}, 然后在get的时候检查一下是否匹配,不匹配的话简单丢弃,这样至少不会遇到莫名其妙的BUG。
Oct 29

php code snippit 不指定

felix021 @ 2015-10-29 10:51 [IT » 程序设计] 评论(0) , 引用(0) , 阅读(1223) | Via 本站原创
转置二维数组:
function transpose($array) {
    array_unshift($array, null);
    return call_user_func_array('array_map', $array);
}


utf-8字符串转为utf-8字符数组:
function utf8_str2arr($str)
{
    preg_match_all("/./u", $str, $arr);
    return $arr[0];
}


按显示宽度截取utf-8字符串
function substr_width($str, $start, $width)
{
    $arr = utf8_str2arr($str);
    $arr_ret = [];
    $i = 0;
    while ($width > 0 and $i < count($arr))
    {
        $arr_ret[] = $arr[$start + $i];
        if (strlen($arr_ret[$i]) == 1) //ascii,width=1
            $width -= 1;
        else
            $width -= 2;
        $i++;
    }
    if ($width < 0)
        array_pop($arr_ret);
    return join('', $arr_ret);
}


让进程在后台运行(detached process),出乎意料地简单
pclose(popen("nohup $cmd &", 'r'));
Oct 16

ssh clone session 不指定

felix021 @ 2015-10-16 11:48 [IT » 软件] 评论(2) , 引用(0) , 阅读(2379) | Via 本站原创
在Windows下用惯了Secure CRT的Clone Session功能,切换到mac下面,还真有点怀念。于是搜了一下,发现达到类似的效果倒是也不难。

1. mkdir ~/.ssh/cm_socket

2. 创建 ~/.ssh/config

  Host *
  ControlMaster auto
  ControlPath ~/.ssh/cm_socket/%r@%h:%p

3. 之后只要是相同的 user@host:port 都会共用一个tcp connection,不需要再输入密码了。

p.s. 补充说明一下,通常来说,能登录服务器也就意味着可以建立信任关系,那么clone session的意义就减少了很多,但是仍然有两个优势:

1. 类似B公司的relay机器,必须使用token认证才能登录的,信任关系不能满足需求(Google Authenticator这样的两步认证方案也类似)

2. 对于延迟比较高的机器,直接复用tcp connection,可以减少相当多的时间(tcp三次握手、ssh认证的各种交互等)。
Aug 18

T02安装Ubuntu 不指定

felix021 @ 2015-8-18 12:42 [IT » 操作系统] 评论(1) , 引用(0) , 阅读(2849) | Via 本站原创
前一段时间在京东众筹购入了一款名为"光棍一号T02"的电视棒,Intel Atom Z3735F(1.33G*4) + 2GB + 32GB,之所以下单,最主要是因为他们家宣称可以支持ubuntu(因为看起来好像就是intel Compute Stick的贴牌产品,而后者是可以支持ubuntu的)。可是到手了以后,问了众筹方才知道,原来安装镜像是不公开的,需要在发货前说明,他们装好再寄过来(跟Compute Stick一个尿性,据说是因为第三方的驱动问题)。没办法,只能自己动手了。

在网上有找到一篇文章:Install Ubuntu 14.04 LTS on the 2GB Intel Compute Stick,里面提供了一个64bit的ubuntu 14.04镜像以及相应的步骤,可以直接安装到2GB内存的Compute Stick上;以及对安装32位UEFI支持的一些说明和提示。

情况是这样的,Intel Compute Stick 2GB+32GB版本出厂预装的是Win8,Ubuntu版本只有1GB内存+16GB存储,所以大神们琢磨着弄一个ubuntu到windows版本上去,可是intel官方因为第三方驱动的原因不开放镜像,于是他们自己做了一个。从文章里可以知道,stick可以在UEFI里面选择启动的系统是32bit的win8还是64bit的ubuntu,也就是说可以按需向操作系统提供32/64位的uefi接口。可是手头的这个T02只有一个非常简单的32bit的uefi,所以整个过程就比较蛋疼了,下面记录一下,备忘。

1. 准备相关材料:http://pan.baidu.com/s/1qWN0jIO

包括 (a) 安装镜像 ubuntu14.04_z3735f.iso (b) 启动盘制作工具rufus-2.2p.exe (c) 支持32位efi的grub启动器 bootia32.efi (d) 安装后用的grub.tar.gz

2. 制作启动盘

使用rufus把iso写入到一个U盘里。把wubi.exe删掉,然后把 bootia32.efi 放到 EFI/BOOT/ 下面

3. 安装使用启动盘启动T02,可以先通过"try ubuntu"体验一下效果。

(a) 如果希望直接安装的话,建议选择"install ubuntu",比较快且不容易出错。
(b) 安装中进行分区的时候选择“使用整个磁盘”,这样会自动使用GPT分区并且创建一个EFI分区。由于本身内存空间有限,建议不要考虑双系统共存了。
(c) 安装期间建议不要插入TF卡,速度超慢。不要安装到TF卡上。

==》 安装完以后不要急着重启

4. 复制文件

(a) 把启动盘上的 bootia32.efi 拷贝到新系统的 [挂载点]/boot/efi/EFI/ubuntu 下面去。
(b) 把grub.tar.gz的内容解压到新系统的随便一个目录中(比如 /home 下面)

5. 重启,进入EFI Shell(因为新系统中没有包含配置好的32位的启动程序,会自动进入到EFI Shell中)

6. 进入grub shell:根据前面列出文章中的Troubleshooting,按顺序执行:

(a) fs0:  (注意冒号)
(b) cd EFI\ubuntu
(c) bootia32.efi  (于是就引入了一个裸的grub shell)

7. 进入initrd中的准系统:在grub shell下面执行

(a) linux (hd0,gpt2)/vmlinuz
(b) initrd (hd0,gpt2)/initrd.img
(c) boot

于是就进入了initrd中的

8. 安装支持32位efi的grub(这一步可能有点坑)

(a) chroot到rootfs
$ mkdir -p /rootfs
$ mount -o rw -t ext4 /dev/mmcblk0p2 /rootfs
$ mount -t vfat /dev/mmcblk0p1 /rootfs/boot/efi/
$ mount --bind /dev /rootfs/dev
$ cd /rootfs
$ chroot .

(b) 安装grub-ia32
这一步记得不是很清楚了,因为各种deb包有奇怪的依赖关系,总之大概是这样的:
$ cd /home
$ dpkg -i *.deb
根据报错信息按需卸载系统中存在的package(dpkg --remove xxx),然后再重新执行上一句直到安装成功。

【注意】安装成功以后、在/rootfs/efi/EFI/ubuntu下面应该有 grubia32.efi 这个启动器。

(c) 重启进入efi shell
是的,因为efi配置里面还是grubx64.efi

9. 从EFI Shell启动进入系统

> fs0:
> cd EFI\ubuntu
> grubia32.efi

一切顺利的话,这里就可以启动进入ubuntu了(真心不容易啊)。

10. 更新EFI配置(依然是参考文章的Troubleshooting)

$ sudo apt-get install efibootmgr #安装EFI配置编辑器
$ sudo efibootmgr -v #查看当前的efi配置(可以看到启动器是grubx64.efi,这个不对!)
$ sudo efibootmgr -b 0003 -B  #删除所有的EFI启动配置
$ sudo efibootmgr -b 0002 -B
$ sudo efibootmgr -b 0001 -B
$ sudo efibootmgr -b 0000 -B
$ sudo efibootmgr -c -d /dev/mmcblk0 -p 1 -l \\EFI\\ubuntu\\grubia32.efi -L ubuntu #加入新的启动配置
$ sync

大功告成!


==== 后记 ====

启动以后就可以按需安装各种package拉,运行速度还不错,wifi没问题,显卡驱动正常(可以调整分辨率),但是声音和蓝牙驱动貌似还是有问题,不过对于一个server来说都是小问题。稍微麻烦一点的是wifi的延迟不太正常,局域网每两次ping的响应,差不多都会有一次是1000ms, 一次是2ms,但是加上一个有线USB网卡以后就都一切正常(这是什么病啊。。),接下来再试用几天看看拉。

@2015-08-19
T02是Intel Atom Z3735f (BayTrail)方案(x86_64 1.33GHz*4 + 2GB ram + 32GB rom),手头有一个2012年8月份入手的mk802(居然已经3年了,依然在勤恳地工作!感谢它!),全志A10的方案(Cortex A8 1GHz*1 + 1GB ram + 4GB rom),二者都安装上了Ubuntu Desktop版,正好作个对比。

实际使用上二者性能上的差距还是蛮直观的,ssh登录的时候,T02马上就进入shell,MK要卡几秒;在桌面程序上,比如Chrome,启动速度、页面加载速度什么的,感受很明显。下面是一些简单的测试数据:

1. 最简单的,编译一个 busy.c 源码(main里面只有一行 while(1)):
T02:0.124s
MK:0.251s

2. 编译bash
以前自己打过patch的一个bash的tar.gz

2.1 解压
T02:0.598s
MK:1.552s

2.2 configure
T02:1min
MK:1min56s

2.3 make
T02:2min16s(make),45s(make -j4)
MK:5m26s

3. CPU温度/频率@T02
这个只测T02:一个720p视频播放、5个busy.c的a.out,测试是否有风扇对着吹的情况
无风扇:81°C/81°C/87°C/87°C,500MHz*4(自动降频)
有风扇:72°C/72°C/79°C/79°C,1333MHz*4(继续吹可能还会再降点温)
Jul 14

vim tips 不指定

felix021 @ 2015-7-14 11:00 [IT » 软件] 评论(0) , 引用(0) , 阅读(1326) | Via 本站原创
~ vimdiff 比对文件的时候,可以使用 "]c" 或者 "[c" 跳转到下一个/上一个不同的地方。

~ retab 将文件中的tab字符按vimrc中指定的格式转换成空格

~ 未完待续
Jul 7

记一个诡异的问题 不指定

felix021 @ 2015-7-7 23:36 [IT » 操作系统] 评论(0) , 引用(0) , 阅读(1321) | Via 本站原创
今天Sandy同学在开发一个网络相关应用的时候遇到了一个奇怪的问题。

大约是这样的一个单例类Foo(以下是类python的伪代码,实际是VB.NET),当调用方法 bar('remove', key, value) 的时候,经常(而不是总是)在for循环过程中报错,错误信息是 "循环过程中_pool已经被修改" 。

class Foo(singleton):
    _pool  = {}
    _mutex = Threading.Mutex()

    def bar(self, action, key, value=None):
        self._mutex.waitOne()

        if action == 'add':
            self._pool[key] = value

        else: #'remove'
            remove_keys = []
            for key, value in self._pool.items():
                if do(key, value):
                    remove_keys.append(key)
            for key in remove_keys:
                del self._pool[key]

        self._mutex.release()

_pool 作为Foo的一个私有成员并且被 _mutex 保护着,理论上是不会出现这个问题的,然而各种排查的表现都指向了线程间的竞争问题,因为只有在调用到 bar('add', key, value) 的时候,_pool才有可能被修改到。

仔细查了一下MSDN上面Threading.Mutex的说明,在备注一栏中藏着一句话:"拥有互斥体的线程可以在对 WaitOne 的重复调用中请求相同的互斥体而不会阻止其执行。",也就是说,如果是同一个线程两次调用 bar 方法的话,这个 _mutex 就相当于失效了。

换用其他的互斥锁机制(例如Syncing)并不解决这个问题(事实上Mutex已经是第三个尝试选项了)。我们甚至试着采用Threading.Senaphore,然而却导致整个进程卡住。

于是debug.print把 Threading.Thread.CurrentThread.ManagedThreadId 输出到控制台,发现在出现错误之前是有多个不同的thread id,但是出错的时候,确实是同一个线程两次调用 bar 方法,也就是说 _mutex 确实不能解决这个问题。

经过各种排查,确认是正好在 do(key, value) 方法调用中出现的,然而 Sandy 同学信誓旦旦地保证,do(key, value) 方法绝对不可能递归地调用回到 bar 函数。由于 do(key, value) 内部调用了某个阻塞的网络请求,据此我推测,.NET的网络模型底层使用了线程+纤程的模型,那只能想办法了。通过查看 bar('add', key, value) 的调用栈,发现确实这是同一个请求,但是中间夹杂了一个未知的"外部过程",也就是说空闲的线程被调度来做其他的事情了。

深坑一个,但是既然找到了原因,就可以考虑如何针对性地去解决它,初步的想法是,Semaphore理论上应该是可以解决这个问题的,可能之前没有细看MSDN、调用方式有点问题。

Sandy同学的想法是,既然 Mutex 已经过滤掉了线程间的冲突,那我们就自己模拟 Semaphore 来解决线程内的冲突,只要简单增加一个初始值为 0 的 _counter 变量 ,在 self._mutex.waitOne() 后面加上
while self._counter != 0:
    Threading.sleep(10) #10ms
self._counter = 1

并在 self._mutex.release() 之前执行 self._counter = 0 就可以了。

想法是美好的,但是一执行就卡死在 foo('add') 调用的 while 循环里。简单分析一下就能发现,这个线程既然一直在while循环里面,就不可能被调度回到 foo('remove') 的纤程去修改 _counter=0,于是就卡住了。

没办法,再回头去仔细看MSDN,Threading.Semaphore 确实没有类似Mutex这样的同线程调用,于是把这个代码按照Example重新写了一遍,但是还是卡死了……

然后我瞬间醒悟过来——这似乎根本就是一个因为.net底层实现导致的死锁!除非上层应用能控制线程调度的细节,否则无论是信号量还是修改过的Mutex(同一个线程不能多次获得的)都不能解决这个问题。于是暂时的结论是可能要采用自己实现的线程池来进行调度,但是改动似乎很大。

完。

UPDATE @ 20150720 后来仔细考虑了下,根本问题是在do(key, value) 内部调用的那个“阻塞”请求,在临界区内本就不该调用阻塞请求。按照.net的文档,那个请求应当是非阻塞的,但是不知道为什么在这里阻塞了。由于我对.net并不了解,我没有再继续追究了。
分页: 5/96 第一页 上页 1 2 3 4 5 6 7 8 9 10 下页 最后页 [ 显示模式: 摘要 | 列表 ]