为什么C++程序跑不过C程序?

C++程序的性能问题往往来源于程序员的误用。比如在不知不觉中创建和销毁很多(不必要的)对象,而程序员往往并不能意识到这一点。当然,容易误用的语言不是一门好的语言。

举个例子,让你按行读取文本文件,你会怎么写?使用全局函数std::getline()吗?

[code lang="cpp" title="代码1"]
void read_line_by_line(std::istream& is) {
std::string line;
while (std::getline(is, line)) {
std::cout << line << std::endl;
}
}
[/code]

还是使用std::istream的成员函数getline()?
[code lang="cpp" title="代码2"]
void read_line_by_line(std::istream& is) {
char line[BUFFER_SIZE];
while (is.getline(line, BUFFER_SIZE)) {
std::cout << line << std::endl;
}
}
[/code]

有趣的是,使用成员函数的代码反倒是C风格,使用全局函数的代码却是C++风格。从性能上看,代码2比代码1要高效得多,因为代码1每读一行就会重新分配一次存储空间(有待验证),而代码2则不必如此。

代码2的缺点在于常量BUFFER_SIZE。如果BUFFER_SIZE小于某行有暗香盈袖长度,while循环便会在读取这一行时退出。性能的提升往往伴随着灵活性的下降。

当然,性能和灵活性并非总是反比的关系,灵活性不好时,性能也未必高。下面这个函数,取自我们实际的项目(便于阐述,略作改动),它在日期时间字符串之间做转换,并且输入输出格式恒定。
[code lang="cpp"]
// \param date "yyyymmdd", such as "20100114"
// \param time "hhmmss", such as "090251"
// \param dateTime "yyyy-MM-ddThh:mm:ss", such as 2010-01-14T09:02:51
const std::string& GmtFromDicomDataTime(const std::string& date,
const std::string& time,
std::string& dateTime)
{
dateTime = date.substr(0, 4) + "-" +
date.substr(4, 2) + "-" +
date.substr(6, 2) + "T" +
time.substr(0, 2) + ":" +
time.substr(2, 2) + ":" +
time.substr(4, 2);
return dateTime;
}
[/code]

这个函数共调用了6次substr(),和10次+操作符。让我们来算算共有多少个临时的字符串。6次substr()调用产生6个临时字符串,10次+操作符产生9个临时字符串,所以一共是15个。我想,没有比这个再多的实现了。

如果要优化的话,鉴于格式恒定,可以先分配好输出字符串的存储,然后将输入字符串的各部分对应到输出字符串中。
[code lang="cpp"]
const std::string& GmtFromDicomDataTime(const std::string& date,
const std::string& time,
std::string& dateTime)
{
dateTime.resize(20);
memcpy(&dateTime[0], &date[0], 4);
dateTime[4] = '-';
memcpy(&dateTime[5], &date[4], 2);
dateTime[7] = '-';
memcpy(&dateTime[8], &date[6], 2);
dateTime[10] = 'T';
memcpy(&dateTime[11], &time[0], 2);
dateTime[13] = ':';
memcpy(&dateTime[14], &time[2], 2);
dateTime[16] = ':';
memcpy(&dateTime[17], &time[4], 2);
return dateTime;
}
[/code]

如代码所示,灵活性没有任何改进,但是毋庸置疑的是,性能肯定有所提高,因为现在不会产生任何临时字符串了。

回到按行读取文本文件的例子,下面这个函数取自一个代码格式化工具,AStyle。我是无意中看到这段代码的,颇为震惊,因为它每次只读一个字符,并且每读一个字符后就调用一次std::string的append()。

[code lang="cpp" title="astyle_main.cpp"]
template<typename T>
string ASStreamIterator<T>::peekNextLine()
{
// ...
// read the next record
inStream->get(ch);
while (!inStream->eof() && ch != '\n' && ch != '\r')
{
nextLine_.append(1, ch);
inStream->get(ch);
}
// ...
}
[/code]
跟这段代码相比,使用std::getline()实在太高效了。有些开源软件的代码质量,实在不敢恭维。

没有评论
2011年11月17日 | 归档于 C/C++
标签: ,

Test Ajax With JQuery in Google App Engine

使用JQuery测试AJAX在Google App Engine中的用法。
如下图所示,在左边文本框输入一些文字,keyup时右边的文本框也显示相应的文字(以{}包裹)。

Ajax Echo GAE

[code title="app.yaml"]
application: ajaxecho
version: 1
runtime: python
api_version: 1
handlers:
- url: /.*
script: echo.py
[/code]

[code lang="python" title="echo.py"]
import os
from django.utils import simplejson
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp import util
# ...
class EchoHandler(webapp.RequestHandler):
def get(self):
template_values = {}
path = os.path.join(os.path.dirname(__file__), "echo.html")
self.response.out.write(template.render(path, template_values))
def post(self):
send_value = self.request.get("sendValue")
# Wrap the value with {} as the echo value.
self.response.out.write(simplejson.dumps({ "echoValue": "{ %s }" % send_value }))
def main():
app = webapp.WSGIApplication([
('/', EchoHandler),
], debug=True)
util.run_wsgi_app(app)
if __name__ == '__main__':
main()
[/code]

[code lang="html" title="echo.html" smarttabs="true"]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>Ajax Echo</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script>
$(function() {
$('#txtA').keyup(function() {
// Call the function to handle the AJAX.
// Pass the value of the text box to the function.
sendValue($(this).val());
});
});
// Function to handle ajax.
function sendValue(txt) {
$.post(
"/",
{ sendValue : txt },
// Function that will be called when server returns a value.
function(data) {
$('#txtB').val(data.echoValue)
},
// The format of the data returned from the server.
"json");
}
</script>
</head>
<body>
<label for="txtA">Type here : </label>
<input type="text" name="txtA" value="" id="txtA" >
<label for="txtB">Echo here : </label>
<input type="text" name="txtB" value="" id="txtB" >
</body>
</html>
[/code]

没有评论
2011年11月15日 | 归档于 Web
标签: , , ,

Pretty Python(可爱的Python)

Pretty Python.pdf

这些slides并不是为了教你怎么入门,所以没有语法、开发环境、应用场景等方面的内容。它侧重于语言层面上的特性,通过一些简单的例子,说明Python的强大和优雅。这里也提到了高阶函数(higher-order function)和列表推导(list comprehension),借此可以看到Python对函数式语言的借鉴。

没有评论
2011年11月14日 | 归档于 Python

Open and Read Files Safely in Python

以读取文件为例,如下代码来自 Dive Into Python 6.2.3。用书里的话说,“下面这个例子展示了如何安全地打开文件和读取文件,以及优美地处理错误。”
[code lang="python"]
try:
fsock = open(filename, "rb", 0)
try:
fsock.seek(-128, 2)
tagdata = fsock.read(128)
finally:
fsock.close()
except IOError:
pass
[/code]

注意里面那个try没有except IOError,因为外面已经有了。所以下面这种写法有点多余:
[code lang="python"]
try:
fsock = open(filename, "rb", 0)
try:
fsock.seek(-128, 2)
tagdata = fsock.read(128)
except IOError: # 此处多余!
# ...
finally:
fsock.close()
except IOError:
pass
[/code]

引入with语句之后的Python,不需要考虑关闭文件,写法更为简洁:
[code lang="python"]
try:
with open(filename, "rb", 0) as fsock:
fsock.seek(-128, 2)
tagdata = fsock.read(128)
except IOError:
pass
[/code]
可以说,with语句就是Python里的RAII。

1 条评论
2011年11月14日 | 归档于 Python
标签:

The Taste of Language(语言的味道)

The Taste of Language.pdf

Agenda

  • 一种语言,方式多样
  • 语言不同,风格迥异
  • 风格虽同,思想有别
  • 尾调用/递归
  • 函数作为第一级对象

这些slides可以作为函数式编程的入门。

没有评论
2011年11月14日 | 归档于 Functional
标签: , , ,