2012年7月27日星期五

Dijkstra 算法

Dijkstra's algorithm
     -- 贪婪技术的一种经典算法,戴克斯特拉算法(维基说法

# 算法逻辑
算法描述 Dijkstra (G, s)
          // 单起点最短路径的Dijstra算法
          // 输入: 具非负权重加权连通图 G = < V, E> 以及它的顶点s
          // 输出: 对于V中的每一个顶点v来说, 从s到v的最短路径的长度dv
          // 以及路径上的倒数第二个顶点pv
          Initialize(Q)     // 将顶点优先队列初始化为空
          for V中每一个顶点 v do
               dv = -1; pv = null;
               Insert(Q, v, dv);     // 初始化优先队列中顶点的优先级
          ds = 0; Decrease(Q, s, ds);
          Vt = {} // 初始化为空
          for i = 0 to |V| - 1 do
               u* = DeleteMin(Q);     //删除优先级最小的元素
               Vt = Vt + { u* };
               for V - Vt 中每一个和u*相邻的顶点u do
                    if du* + w(u*, u) < du
                         du = du* + w(u*, u);
                         pu = u*;
                         Decrease(Q, u, du);

# 算法效率
分析该算法的时间复杂度,假设对应的连通图边数 m 和顶点数 n 。
1, 最简单实现方法,以一个链表或者数组来存储所有顶点的集合Q,而搜索Q中权值最小元素操作DeleteMin只需线性搜索为复杂度O(n)。该算法整体的时间复杂度是 O(n^2)。

2, 用邻接表来更有效的实现该算法,同时以二叉树结构作为优先队列存Q,则搜索权值最小元素DeleteMin时间复杂度为Q(log n),此时该算法整体的时间复杂度为O((m + n)log n),

# 示例说明
(还是伪码,从代码提炼出来的伪码,这样看起来应该比较浅显易懂。)
void Dijkstra (Graph& G)
{
Queue Q; Path P;
InitQueuePath(Q, P);
for (int s=0; s< G.points;s++) {
Decrease(Q, s, 0);
for (int i=0; i < G.points-1; i++) {
int u = DeleteMin(Q, G.flag, G.points);
if (u==-1)
break;

for (int j=0; j< G.points; j++) {
if ( G.flag[j]==false && ((__int64)Q[u]+G.A[u][j])<Q[j]) {
P[j] = u;
Decrease(Q, j, Q[u]+G.A[u][j]);
}
}
}
for (int i=0; i < G.points; i++)
G.A[i][s] = G.A[s][i] = Q[i];
}
}

完整的C++示例代码存放在github

# 引用扩展

参见维基上伪码描述如下:

在下面的算法中,u := Extract_Min(Q) 在顶点集合 Q 中搜索有最小的 d[u] 值的顶点 u。这个顶点被从集合 Q 中删除并返回给用户。

function Dijkstra(G, w, s)
     for each vertex v in V[G]                        // 初始化
             d[v] := infinity                         // 将各点的已知最短距离先设置成无穷大
             previous[v] := undefined                 // 各点的已知最短路径上的前趋都未知
     d[s] := 0                                        // 因为出发点到出发点间不需移动任何距离,所以可以直接将s到s的最小距离设为0
     S := empty set
     Q := set of all vertices
     while Q is not an empty set                      // Dijkstra演算法主體
             u := Extract_Min(Q)
             S.append(u)
             for each edge outgoing from u as (u,v)
                      if d[v] > d[u] + w(u,v)         // 拓展边(u,v)。w(u,v)为从u到v的路径长度。
                              d[v] := d[u] + w(u,v)   // 更新路径长度到更小的那个和值。
                              previous[v] := u        // 记录前趋顶点

如果我们只对在 s 和 t 之间寻找一条最短路径的话,我们可以在第9行添加条件如果满足 u = t 的话终止程序。
通过推导可知,为了记录最佳路径的轨迹,我们只需记录该路径上每个点的前趋,即可通过迭代来回溯出 s 到 t 的最短路径(当然,使用后继节点来存储亦可。但那需要修改代码):
 s := empty sequence 
 u := t
 while defined u                                        
       insert u to the beginning of S
       u := previous[u]      //previous数组即为上文中的p
现在序列 S 就是从 s 到 t 的最短路径的顶点集。

关于“Dijkstra 算法”更详细的文字描述可以参见维基百科百度百科

2012年7月25日星期三

static_cast vs reinterpret_cast vs dynamic_cast


static_cast vs reinterpret_cast vs dynamic_cast


> static_cast

  用法:static_cast<type-id> (expression)
  该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
  用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
  进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
  进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
  用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
  把空指针转换成目标类型的空指针。
  把任何类型的表达式转换成void类型。
  注意:static_cast不能转换掉expressionconstvolatile、或者__unaligned属性。
  C++static_castreinterpret_cast的区别
  C++primer第五章里写了编译器隐式执行任何类型转换都可由static_cast显示完成;reinterpret_cast通常为操作数的位模式提供较低层的重新解释
  1C++中的static_cast执行非多态的转换,用于代替C中通常的转换操作。因此,被做为隐式类型转换使用。比如:
  int i;
  float f = 166.7f;
  i = static_cast<int>(f);
  此时结果,i的值为166
  2C++中的reinterpret_cast主要是将数据从一种类型的转换为另一种类型。所谓通常为操作数的位模式提供较低层的重新解释也就是说将数据以二进制存在形式的重新解释。比如:
  int i;
  char *p = "This is a example.";
  i = reinterpret_cast<int>(p);
  此时结果,ip的值是完全相同的。reinterpret_cast的作用是说将指针p的值以二进制(位模式)的方式被解释为整型,并赋给i//i 也是指针,整型指针;一个明显的现象是在转换前后没有数位损失。
 ==================================================

reinterpret_cast
  用法:reinterpret_cast<type-id> (expression)
       reinterpret_cast是为了映射到一个完全不同类型的意思,这个关键词在我们需要把类型映射回原有类型时用到它。我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的。 (这句话是 C++编程思想中的原话 )
  static_cast reinterpret_cast的区别主要在于多重继承,比如
  class A { public: int m_a; };
  class B { public: int m_b; };
  class C : public A, public B {};
  那么对于以下代码:
  C c;
  printf("%p, %p, %p\r\n", &c, reinterpret_cast<B*>(&c), static_cast <B*>(&c));
  前两个的输出值是相同的,最后一个则会在原基础上偏移 4个字节,这是因为 static_cast计算了父子类指针转换的偏移量,并将之转换到正确的地址( c里面有 m_a,m_b,转换为 B*指针后指到 m_b处),而 reinterpret_cast却不会做这一层转换。
  因此你需要谨慎使用 reinterpret_cast。

 >> reinterpret_cast 特殊用途
使用reinterpret_cast关键字的一个特殊用途,用于C++实现哈希函数的数值转换。(该数值转换即类C(语言)的强制转换,不安全性。) msdn上面示例说明如下:

One practical use of 
reinterpret_cast is in a hash function, which maps a value to an index in such a way that two distinct values rarely end up with the same index.

// expre_reinterpret_cast_Operator.cpp
// compile with: /EHsc
#include <iostream>

// Returns a hash code based on an address
unsigned short Hash( void *p ) {
   unsigned int val = reinterpret_cast<unsigned int>( p );
   return ( unsigned short )( val ^ (val >> 16));
}

using namespace std;
int main() {
   int a[20];
   for ( int i = 0; i < 20; i++ )
      cout << Hash( a + i ) << endl;
}
The reinterpret_cast allows the pointer to be treated as an integral type. The result is then bit-shifted and XORed with itself to produce a unique index (unique to a high degree of probability). The index is then truncated by a standard C-style cast to the return type of the function.

dynamic_cast
用法:dynamic_cast<type-id> (expression)
dynamic_cast把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void*;
如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。
dynamic_cast运算符可以在执行期决定真正的类型。如果downcast是安全的(也就说,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。如果downcast不安全,这个运算符会传回空指针(也就是说,基类指针或者引用没有指向一个派生类对象)。
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

基类中要有虚函数,否则使用dynamic_cast会编译出错,而static_cast则没有这个限制。
因为基类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时dynamic_cast转换才有意义。
这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见<Inside c++ object model>)中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。
另外,dynamic_cast还支持交叉转换(cross cast)

dynamic_cast操作失败的情况,其返回值根据type-id类型不同,
1. type-id为指针类型,则返回NULL空指针。
2. type-id为引用类型,则抛出一个bad_cast异常。
---
When dynamic_cast cannot cast a pointer because it is not a complete object of the required class -as in the second conversion in the previous example- it returns a null pointer to indicate the failure. If dynamic_cast is used to convert to a reference type and the conversion is not possible, an exception of type bad_cast is thrown instead.


dynamic_cast can also cast null pointers even between pointers to unrelated classes, and can also cast pointers of any type to void pointers (void*).
---

> const_cast


以上内容整理了C++语言的几种类型转换的用法、说明以及使用时注意点,包括static_cast vs reinterpret_cast vs dynamic_cast。唯一确少的const_cast,它是用于常量与非常量之间的类型转换,对其理解与使用相对比较简单,所以可忽略提及,可参加cppreference百度百科

> 参考资料
该文章源自于网站资料的收集与整理,参考资料引用自:
1.  Type Casting from cplusplus。
2.static_cast 百度百科
3. reinterpret_cast cppreferencemsdn
4. dynamic_cast 百度百科msdn






2012年7月18日星期三

用命令行搜索你的Gmail


用命令行搜索你的Gmail


     --

分享使用Gmail高级搜索经验(经历)



> 故事  

   先讲一段关于我的Gmail的故事吧,好像几个月前的事情了。

     我的Gmail里有个工作文件夹(即专门收发工作邮件的地方),平时我都会及时读取我的工作邮件的所以一般情况下该文件夹均已读状态。但是,某一天我的工作文件夹,(冒然)始终显示着有未读邮件(即标签名加粗显示)而且只有一封,而我翻了好几页的邮件也未见一封未读邮件啊。由于差不多两年工作时间积累下来的(工作)邮件很多很多,将近2K封,所以这样一页一页(一页显示50封)翻找下去的确有些痛苦不已,果断不干了再另想他法。

     ……

     后来,我想着既然Google这么牛逼的,那Gmail应该有提供命令模式的邮件搜索的吧,而且可以定制找你想找的那封邮件,只要你的相信足够。所以我在Google搜索上面找到了这篇博文,很快就帮我找到那封烦人的未读邮件。只要在Gmail页面前面的搜索框输入一命令然后回车即找到了。其命令很简单,如下:

label: huangjunkun@xunlei.com  is:unread 
     题外话,这封未读邮件很奇怪,明明是某同事刚刚(事情的昨天)发送的,结果由于其邮件显示时间为去年时间(与实际相差一年),导致该邮件被Gmail显示在后面后面的邮件堆里头了,隔着一年的邮件怪不得令我这么难找的啊!最后,找到了出现这样问题的根源了,某同事他的电脑上面显示的系统时间竟然错误,其年份相差一年(2011)。额,无语了 …… 


> 命令说明
     再后来,我在Gmail的官方帮助文档找到了更齐全的说明文档,专门介绍Gmail高级搜索运算符的,内容如下:

高级搜索运算符是用来在 Gmail 搜索中执行特殊操作的查询字词或符号。这些运算符可让您快速、准确地找到所要查找的内容。这些运算符也可用来设置过滤器,这样您便可以自动整理收件箱。下面列出了一些最有用的运算符。
您也可以点击搜索框中的箭头来优化您的搜索。

运算符定义示例
from:用于指定发件人示例:from:amy
含义:来自 Amy 的邮件
to:用于指定收件人示例:to:david
含义:您或其他人发送给 David 的所有邮件
subject:搜索主题行中的字词示例:subject:dinner
含义:主题中含有字词“dinner”的邮件
OR搜索与条件 A 或条件 B 匹配的邮件*
*OR 必须为全大写字母形式
示例:from:amy OR from:david
含义:来自 Amy 或 David 的邮件
-
(连字符)
用于排除不包含您的搜索字词的邮件示例:dinner -movie
含义:包含字词“dinner”,但不包含字词“movie”的邮件
label:按标签搜索邮件*
*没有可用于搜索无标签的邮件的运算符
示例:from:amy label:friends
含义:来自 Amy 且带有标签“friends”的邮件
示例:from:david label:my-family
含义:来自 David 且带有标签“My Family”的邮件
has:attachment搜索包含附件的邮件示例:from:david has:attachment
含义:来自 David 且包含附件的邮件
list:搜索与邮寄名单相关的邮件示例:list:info@example.com
含义:标头中包含字词“info@example.com”的邮件、发送到此名单的邮件或从此名单发送的邮件
filename:按名称或类型搜索附件示例:filename:physicshomework.txt
含义:包含名为“physicshomework.txt”的附件的邮件

示例:label:work filename:pdf
含义:带有标签“work”且附件为 PDF 文件的邮件
“ ”
(引号)
用于精确搜索短语*
*不考虑大小写
示例:“i'm feeling lucky”
含义:包含短语“i'm feeling lucky”或“I'm feeling lucky”的邮件

示例:subject:"dinner and a movie"
含义:主题中包含短语“dinner and a movie”的邮件
( )用于组合字词
用于指定不应排除在外的条件
示例:from:amy (dinner OR movie)
含义:来自 Amy 且包含字词“dinner”或“movie”的邮件

示例:subject:(dinner movie)
含义:主题中同时包含字词“dinner”和“movie”的邮件
in:anywhere搜索 Gmail 中所有位置的邮件*
*默认情况下,垃圾邮件已删除邮件被排除在搜索范围之外
示例:in:anywhere movie 
含义:所有邮件垃圾邮件已删除邮件中所有包含字词“movie”的邮件
in:inbox
in:trash
in:spam
收件箱已删除邮件垃圾邮件中搜索邮件示例:in:trash from:amy
含义:已删除邮件中来自 Amy 的邮件
is:important
label:important
搜索优先收件箱中的重要邮件。示例:is:important from:janet
含义:来自 Janet 且被优先收件箱标记为重要的邮件
is:starred
is:unread
is:read
搜索已加星标、未读或已读的邮件示例:is:read is:starred from:David
含义:来自 David 且已加星标的已读邮件
has:yellow-star
has:red-star
has:orange-star
has:green-star
has:blue-star
has:purple-star
has:red-bang
has:orange-guillemet
has:yellow-bang
has:green-check
has:blue-info
has:purple-question
搜索带有特殊星标的邮件示例:has:purple-star from:David
含义:来自 David 且使用紫色星标进行标记的邮件
cc:
bcc:
用于指定抄送:密送:字段中的收件人*
*搜索“密送:”找不到密送给您的邮件
示例:cc:david 
含义:抄送给 David 的邮件
after:
before:
搜索在特定时间段内发送的邮件*
*日期必须采用 yyyy/mm/dd 的格式。
示例:after:2004/04/16 before:2004/04/18 
含义:在 2004 年 4 月 16 日到 2004 年 4 月 18 日之间发送的邮件。*
*更确切地说,是在 2004 年 4 月 16 日 00:00 到 2004 年 4 月 18 日之间发送的邮件。
is:chat搜索聊天讯息示例:is:chat monkey
含义:包含字词“monkey”的任何聊天讯息。
deliveredto:搜索在邮件标头的“Delivered-To”行中包含特定电子邮件地址的邮件示例:deliveredto:username@gmail.com
含义:在邮件标头的“Delivered-To:”字段中包含 username@gmail.com 的任何邮件(邮件标头可帮助您查找从其他帐户转发的邮件或发送给某个别名的邮件)。

已更新 12/07/2011

     看完如上Gmail高级搜索运算符的说明(结合表格示例),那么我们可以很简单的在Gmail搜索你想找的邮件了。相信,这样你就可以找到你有印象的邮件了。Enjoy it.

     或是你有相关更好的资料分享,欢迎联系我☺。


-- 201203
     (完)

2012年7月16日星期一

谈谈内存越界读写的危险

谈谈内存越界读写的危险
  -- 记某一次低级而糟糕的程序犯错。


# 故事
这是发生在上周的事情了。当时就有想法,要写篇文章以表示‘铭记’。

想必,这个话题对于C/C++程序员一定很熟悉,都有过或多或少,或轻微或严重(惨痛)的经历吧。

最近正在开发的项目,正在内部开发人员自己测试阶段,突然爆出好多的程序崩溃,大概估摸了下这些崩溃的规律如下:

     1,程序崩溃不确定性但必崩,只要你多操作几次,或重启程序再来试试。
     2,这些崩溃基本都不一样,(从堆栈看来)分散在不同的几个模块;
     3,分析程序崩溃堆栈看到崩溃在正常的代码上(显然不可能),亦看不出错误代码地方了。
     4,由于有辅助程序可在程序崩溃时候帮助自动抓取程序堆栈。但是有时程序崩溃无堆栈或堆栈数据无效。
……

最后,辛苦地安分地回头好好Review最近提交的代码,(……由于这几次提交改动均无法自己做到完整的测试保证,只能自己简单的功能程序和代码Review……),涉及的代码量较大,所以花了不少时间才找到了错误所在,有地方内存越界读写了。虽然找到了错误并很快纠正过来了,但是那样的低级错误让吾情何以堪?也正是为了纪念这么一‘尴尬’,我才想整理一篇博文表示‘罪过’。前车之鉴,后车之师。


个人觉得,上面总结的规律四点不妨可作为“内存越界读写”这样错误的表象。若以后还是遇见这样情况,不如先怀疑怀疑你的内存被越界读写了,赶紧回头仔细Review代码吧,相信你已很快顺利找到错误地方……

# 示例
下面内容就简单描述一下如何导致程序乱蹦,即越界“乱踩”内存了!示例代码如下:

struct A {
     ...
};
struct B {
     ...
     A a;
     ...
};

// 错误程序如下:
void some_func(struct B* b_ptr) {
     ...
     memset(&(b_ptr->a), 0, sizeof(*b_ptr)); // Error: memory overwrite.
     ...
}

// 修正程序如下:
void some_func(struct B* b_ptr) {
     ...
     memset(&(b_ptr->a), 0, sizeof(b_ptr->a));    // 1. ok
     // memset(&(b_ptr->a), 0, sizeof(struct A)); // 2. or else, also well.
     ...
}

果然吧,错误的程序那么低级,So stupid,而找到了错误修正程序那么简单的!程序就恢复正常了。
我想当时写程序脑子犯晕了吧。好了,不再多说了。还是我以前的那么句话,“以后写程序时候头脑得时刻保持精神点,否则后果有你好受的。”

# 借鉴
最后顺便引用一段关于内存越界的描述吧,延伸看看,想想。
--
对Memory overwrite 和memory corruption有什么好办法哪?常见的,就是设置CPU的breakpoint,也就是对某一地址的写操作,dump出stack,然后找是哪个模块写坏的。或者可以把整个page设置成只读,在操作系统层面检查,然后dump。这些对只读的变量或者page还还用,但是对可读写的变量又该怎么办哪?如何区分合法的读写和非法的读写?
对于非法的读写,也有在语言层次进行控制的,比如c++/java/c#等面向对象的语言,就有对对象的保护。但是这些语言并没有排除全局变量,也就是说,在语言层次的控制并不彻底。基于语言的静态检查是个好办法,但是对大规模代码,检查也需要时间和精力。任何经过仔细review的代码都是好代码,但是,有哪些公司能够坚持严格的代码review哪?
对于第三方和legacy的代码,review也基本是不现实的,但是静态检查是第一道防线,一定要坚持。
在操作系统层面,没有对象的概念,导致在操作系统层面没法保护。或者说,操作系统层面的保护代价太大了。我觉得还是操作系统的设计思想没有体现保护,隔离的要求,传统操作系统在这方面,做的很不够。
新的操作系统应该能够在更细粒度上做到隔离,保护,但是性能,通信等等又如何解决?

-- 

还有,在stackoverflow上面关于内存越界的一段讨论,这里

(完)




2012年7月12日星期四

工程师如何不被PM欺负(转)


工程师如何不被PM欺负
老师教我们怎么写程序,但从来没告诉我们在公司里,会有个叫做PM的人每天分派作业给我们,还逼著我们赶快做完。这是许多软件工程师进入职场的第一个惊喜。隔了不久,还会发现,这些可能把你压得死死的PM,多半一行程序都不会写。于是我们会面临一种很矛盾的心情,有时候会是一种有点被欺负的心理。这篇文章是前一篇文章PM如何突破工程师的心防的延伸,我们讨论的是工程师在这样状况下的生存之道。
工程師如何不被PM欺負

(1) 提高自己的能见度
在非常多的公司,上层的老板或公司的大老板只看得到一个project的PM,而看不到背后辛苦的工程师。也就是说,你的努力和成果,被遮敝了。我一直相信在职场上,让自己在老板或其他同事前有「能见度」是重要的。能见度除了在很多状况下(会议发言、讨论…)可以显现出来外。提供一个我有个朋友很厉害的一招给各位参考。身为一个工程师的他,在每个大的project进行完后,都会「不经意」的寄出一封「谢谢信」给参与这个project的每个人,顺便cc给本来根本不知道他在做什麼的大老板。信裡面一一点名感谢每个人给他的指导和这个project的协助。这种信每个人看了都很高兴,最重要的是最后大老板也对他有了深刻的印象。
(2) 不要每天只埋头写程式
工程师大部份很喜欢埋头写程式,因为这是自己最擅长,也是最不花力气的事情。但如果你每天100%时间写程式,我保证你会自我感觉良好,但是所有人都不知道你在做什么。所以也许该换换策略,让自己的时间有多一点的部份是用来「表现自己」。「表现自己」不代表做一些表面功夫浪费时间。而是以你的角色,来参与讨论,给出有意义的建议。工程师很喜欢只用电脑和其他人沟通,想要进度都用一个系统来追踪,想法都用email来讨论。在职场上,很重要的是你要学习少用email,多走过去和那个人说话。也许走过去多花了1分鐘,但是你和其他人互动良好,会让你在职场上过得比较顺利。
(3) 站在老板的角度想事情
工程师由于角色的关系,非常容易会站在「技术」的角度想事情,但往往常被主管否决而觉得灰心。公司的想法通常和PM的想法比较接近,都是站在公司的利益想事情,极少用「技术」的角度想事情。你要试著跟他们想的一样,你的日子才会过得快乐。举例来说: 假如我们公司现在要输入10000笔资料。有两个方案,方案A是「手动输入」,方案B是「用程式自动汇入」。方案A要请10个工读生,一笔一笔输入几乎都没有差太多的资料。方案B是支无敌厉害的程式,你开发一天,程式跑3秒鐘就全部完成。但评估起来方案A的总体成本比方案B还要低。我相信极大多数的公司经营者,都会愿意找来10个人,做著重复的事情,一笔一笔key in资料。如果你以工程师的角度来想,你可能会觉得「这个这麼简单,一支程式就好了」,然后开始觉得老板选择方案B真迂腐。你要试著让你的大脑跟公司的利益sync,这样会让你好过很多。因為绝大多数的PM都知道他们的大脑要怎麼跟老板sync。在老板面前让自己显得比PM聪明的方法只有一个,那就是大脑和公司利益的sync做得比PM还彻底。
(4) 用PM害怕的弱点有效去争取更多开发时间
PM很喜欢每个东西都如期上线,如果提早上线就更好。很多人会因为deadline而跟PM翻脸,这是不智的。回到我那位工程师朋友的例子,他会和顏悦色的对PM说「我可以每天熬夜来把它做完,有可能可以如期上线,但我知道它会出现很多『我们』现在都没想到的问题,那可能会让老板(或客户)觉得我们很不仔细。但如果你可以帮我争取多一点时间,我可以让它品质好很多。」对PM来说,除了要「快」以外,东西如果出来很烂,也打到了他的痛点。我的工程师朋友用这个方法帮自己争取到了比较长的开发时间,和更好的睡眠。
(5) 用PM的语言和他沟通
很多工程师会习惯用自己的语言和PM沟通,於是造成沟通不良。我们得试著让自己对他们的谈话,是世界上任何一个人都听得懂的语言。尽量少提技术的术语,尽量少让PM觉得你用你的技术优势在打压他。因为PM不可能学会工程师的语言,所以你们唯一能沟通的可能,就是你学会用PM的语言。
(6) 变成工程师团队里面最受PM们欢迎的人
你会发现,如果叫PM们投票,从最喜欢合作的工程师,排到最不喜欢合作的工程师。大家的清单常常非常一致。而且你会发现,在清单名列前矛的人,通常在职场上容易步步高升。所以,想办法变成那个人吧! 因为PM们对你的评价,往往在公司里,和你的工程师主管对你的评价同样重要。
(7)上班前三个月,不要试着改变公司任何东西
公司的系统、公司的project、流程,所有的东西。会是现在这个样子,都必定有它的原因。有理性的原因,也有不理性的原因,也可能它的原因就是没有原因。但绝大多数的公司找你进去,是想要你把一个东西,在他「现在的架构」下开发出来。在前三个月,如果你觉得大家用的开发环境很烂、测试的流程很烂、任何平台很烂。请先忍耐一下,因为除了非常非常open minded的主管和同事,绝大多数的人不会对你刚进来就想改变一切的想法太欢迎。
(8) 归功给PM
EQ好的PM会把project归功给工程师。但做为工程师的你,如果EQ够好,应该再把它归功给PM。不要因為这是你写的code,就认為这是你自己做出来的。因为这样除了自己感觉良好外,对职场生存没有帮助。想办法「言必谈PM」。把自己和PM当成一个team,这个project是我们一起做出来的。虽然很多PM会戏称自己是在旁边帮忙打杂的,但是他会很感谢你很体贴的把一些功劳归于他。
(9) 不要为了enjoy自己的成就感,浪费公司的资源
很多工程师喜欢把公司当lab,去试验一些新的技术。如果这对公司「真的有帮助」的话,那当然很好。在做这些事或提议前,请试着用老板的角度想,在公司利益最大化的前提下(而非个人学习或成就感),他会不会打从心裡支持你做这样的试验。如果不会,那就千万不要做。因为在你做的很开心的同时,别人可能觉得这只是在浪费公司资源。
(10) 变成一个更像PM的人
在技术上你应该向你其他工程师同事看齐,但在「性格」或「行为」上,通常你应该去模仿PM team的人。请相信我,在绝大多数公司,「性格」和「行為」近似于PM的工程师,在公司裡是最吃香的。
写这篇文章,也许还会再得到一些批评。但我只是单纯善意的,想告诉工程师们。我们应该提高自己的能见度,适度的让其他人看到我们的表现。以及让自己变成一个外表看起来像PM的工程师,通常在公司裡会过得蛮好的。很多工程师会觉得自己被PM欺负,但PM通常不会欺负长得和他们一样的人。如果你喜欢这篇文章,也许你可以再看看这篇: PM如何突破工程师心防


注:文章转载自伯乐在线,原文在这里



2012年7月9日星期一

内存的分配与回收(笔记)


内存的分配与回收
     -- =(内存管理)分区的分配与回收

1. 固定分区时的分配与回收。
     关键点,需要一张描述不用分区使用情况的分区说明表。
     分配时,只要存储管理程序根据请求查询分区说明表(或链表),从中找出一个满足要求的空闲分区,将其分配给申请者。
     回收时,操作就更简单了,不需要内存资源了,管理程序只要将对应的分区状态置为为使用即可。

2. 动态分区时的分配与回收。
     不同于固定分区方案,该方案需主要解决三个问题:
     1)分配程序(算法),从分区表寻找(最)合适的空闲分配出去。
     2)分配之后,更新分区表。
     3)回收算法,回收(释放)分区时需和相邻的空闲区进行链接合并,更新分区表。

     分配算法有三种常用算法包括最先适应法(first fit algorithm),最佳适应法(best fit algorithm)和最坏适应法(worst fit algorithm)。

     a. 最先适应法,要求分区表(表或自由链表)按起始地址递增的次序排列。该算法的最大特点是一旦找到大于或等所要求内存长度的分区则结束搜索。接着,从找到的分区划分要求的长度分配出去,并把余下部分与相邻分区合并(若相邻空闲区存在的话),更新分区表。
          1. 搜索分区表空闲区大小大于或等于申请大小alloc_size,若找到则继续2,否则停止分配失败;
          2. 找到合适空闲区A,若其大小a_size==alloc_size,直接标记分区A为“使用区”完成。
          3. 若a_size > alloc_size,切分A为A1(alloc_size),A2(a_size-alloc_size),标记A1“使用区”,A2“空闲区”。

     b. 最佳适应法,从分区表寻找最合适的空闲区分配。这里的最合适标准可以认为是空闲区的大小与要求的大小相差最小。
          1. 遍历分区表找最接近于申请大小alloc_size空闲区B,若找到则继续2,否则停止分配失败;
          2. 找到最合适空闲区B,若其大小b_size==alloc_size,直接标记分区A为“使用区”完成。
          3. 若b_size> alloc_size,切分B为B1(alloc_size),B2(b_size-alloc_size),标记B1“使用区”,B2“空闲区”。

   c. 最坏适应法,从分区表寻找最大的空闲区分配。
          1. 遍历分区表找最大空闲区W,大小为w_size,若w_size >alloc_size则继续2,否则停止分配失败;
          2. 找到最合适空闲区W,若其大小w_size==alloc_size,直接标记分区A为“使用区”完成。
          3. 若w_size> alloc_size,切分W为W1(alloc_size),W2(w_size-alloc_size),标记W1“使用区”,W2“空闲区”。

     (详细介绍可从网络搜索参考资料)


3. 几种分配算法的比较

     a. 从搜索速度上看,最先适应算法具有最佳性能。另一个有点就是尽可能地利用了低地址空间,从而保证高地址有较大的空闲区来放置要求内存较多的进程或作业。
     b. 最佳适应法找到的空闲区是最佳的,最合适用户要求的可用空闲区。但这样做在某些情况下并不一定提高内存的利用率。倒是可能导致内存碎片更多,因为分区表可能剩下越来越多的“小碎片”不能满足后续使用。
     c. 最坏适应算法正是基于不留碎片空闲区这点出发的,它选择最大空闲区来满足申请的要求,以期分配后的剩余部分仍能后续分配使用。


最后,总之三种算法各有特长,针对不同的请求队列,效率和功能不一样。实际项目开发使用,可以适度地多者结合一起使用。

……

(完)