Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/error handling in paddle #1149

Merged
merged 13 commits into from
Jan 19, 2017

Conversation

reyoung
Copy link
Collaborator

@reyoung reyoung commented Jan 14, 2017

错误处理应该是Paddle API比较重要的一方面,因为把Paddle作为一个库引入的话,调几个函数就log(FATAL)了肯定不合适。

这里的Status是不是可以暂定为Paddle的错误类型?Status基本上是一个std::string的指针。如果为空,那么便没有error,否则error的详细信息是这个字符串的内容。

这种实现下,如果没有错误的话,返回这个错误码是基本没有性能开销的(和返回一个整数开销应该一样)。如果返回错误的话,开销在指针引用上(主体可能是多线程的shared_ptr锁),不会复制字符串很多次。

@reyoung
Copy link
Collaborator Author

reyoung commented Jan 16, 2017

@wangkuiyi @hedaoyuan 我们用这个Status对象做Paddle的错误值合适么?

Copy link
Contributor

@hedaoyuan hedaoyuan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

另外,写个ISSUE描述一下这个工作吧。最主要的是,要让别人明白Status怎么用;以及要让别人知道,后续写哪些东西的时候要用Status作为返回值。

* @brief isOK
* @return true if OK.
*/
inline bool isOK() const noexcept { return errMsg_ == nullptr; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(!errMsg_), compares shared_ptr with a null pointer
http://en.cppreference.com/w/cpp/memory/shared_ptr/operator_cmp

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

其实,感觉maybe这两种写法在release模式下没有啥性能区别。。。我回头测试一下。。

XXX==nullptr感觉可读性反而好一点。

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

测试了一下,判断shared_ptr是否为空。使用隐式类型转换成bool型,或者直接和nullptr比较,在clang 8里,-O3下,二者生成的汇编完全一致。没有差别。

感觉还是用 xxx == nullptr 或者 xxx != nullptr 可读性好一点。

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

使用隐式类型转换成bool型 !errMsg_ 并不是隐式类型转换,是调用了operator bool();errMsg_ == nullptr; 也是调用了!errMsg_。

7) !lhs
8) !rhs
9) (bool)lhs
10) (bool)rhs

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

恩,都是inline的调用啦,-O3都会没有的 :-)


#include <gtest/gtest.h>

TEST(Status, testAll) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TEST 除了check Status本身以外,还可以去构造一些实际用途的Case。
比如,构造一些函数调用,错误后返回Status这样的例子,也能比较好的让别人知道Status的用法。

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

写到这个类的注释里面了。

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

写到类的注释里面了。

*/
template <typename... ARGS>
inline void setByPrintf(const char* fmt, ARGS... args) noexcept {
constexpr size_t kBufferSize = 4096;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4096,需要这么大。。。

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to 1024

Copy link
Collaborator

@wangkuiyi wangkuiyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

提了一些建议。我担心这个工作有点儿over engineering了。用了很多C++的路子。但是貌似都是C就能做的。

* Although Status inherits the std::exception, but do not throw it except you
* know what you are doing.
*/
class Status final : public std::exception {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

为什么要从 std::exception 派生呢?定义成 更简单的形式,比如

class Error {
 private:
  const char *msg_;
}

不行吗?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

派生了也没啥坏处。。std::exception算是C++的通常惯例。。

const char* msg_;和这个事情是两件事,如果用const char* msg_; 那我们还得手写一个复制构造函数,而且Error的传递从之前的引用传递变成了值传递。
将msg_定义成shared_ptr,那么普通的赋值Error对象,就是引用传递了。这玩意其实是和golang里面的那个error语意一致的写法。

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我了解需要 std::shared_ptrstd::string 的原因了。

回到派生——我建议从最简化的原则出发“如果没有特别的好处,我们就不要引入这个派生了“。

namespace paddle {

/**
* Status is Paddle error code. It only contain a std::string as error message.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果只包含一个error message,那么叫Error就好了。比如Go的error type

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

有道理。

* @brief set a error message for status.
* @param msg
*/
inline void set(const std::string& msg) noexcept {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议用一个全局friend函数(比如叫做Errorf),而不是用Error的method来创建内容。因为一个error的语法应该是“一旦创建不能被修改”,所以method,尤其是名为set的method不能贴切地表现这个syntax。

Error Network.allocateParameters() {
  ...
  if (...) {
    return Errorf("Cannot allocate memory block of size %d", kBufferSize);
  }
  ...
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* create a error status by C style printf.
*/
template <typename... ARGS>
inline static Status printf(const char* fmt, ARGS... args) noexcept {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不需要很多风格的error creation吧。

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

/**
* @brief what will return the error message. If status is OK, return nullptr.
*/
const char* what() const noexcept override {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

为什么需要 override 和 noexcept 关键词呢?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

因为这玩意其实还是继承了std::exception,符合标准的C++ exception的定义。

虽然我们不用Exception,但是借用这个接口定义我们的error应该是可以的。

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

因为没有特别的好处,所以别派生了,读者也就不需要了解override和noexcept这两个关键字了。毕竟C++里的大部分发明都是利弊兼备的,读者也被code style帮助着尽量少了解这些利弊之争。

/**
* @brief what will return the error message. If status is OK, return nullptr.
*/
const char* what() const noexcept override {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what ==> msg?

这样用的时候就是 error.msg():

auto err = Errorf("...);
LOG(ERROR) << err.msg();

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what函数名是C++对于exception的普遍约定。虽然Paddle不鼓励使用exception,但是按照C++的普遍约定命名也比较有道理。

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同上,不用exception,也就不用what了。

inline bool isOK() const noexcept { return errMsg_ == nullptr; }

private:
std::shared_ptr<std::string> errMsg_;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里就是一个 const char * msg_ 就好了吧。

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在上面回复了一下。

@reyoung
Copy link
Collaborator Author

reyoung commented Jan 18, 2017

@wangkuiyi @hedaoyuan Partially follow comments. Done.

@wangkuiyi
Copy link
Collaborator

贴一个我想到的简单实现:

#include <stdarg.h>

#include <string>

// We try to make error looks like a native POD type, so we broke the
// naming convention for C++ classes.
//
// The following example shows supported syntax of error:
/*
  error nomem("Cannot allocate %d bytes", 100);  // 1. ctor is like printf.
  if (nomem) {                                   // 2. type conversion to bool.
    printf("An error: %s", nomem.msg());         // 3. retrieve message.
  }
  error e = nomem;  // 4. feel free to copy.
 */
class error {
public:
  explicit error(const char* format, ...);
  operator bool () const { return msg_.get() != NULL; }
  const char* msg() const;

private:
  std::shared_ptr<std::string> msg_;
};

error::error(const char* format, ...) {
  char buffer[256];
  va_list ap;
  va_start(ap, format);
  vsnprintf(buffer, 256, format, ap);
  va_end(ap);
  msg_.reset(new std::string(buffer));
}

const char* error::msg() const {
  if (msg_.get() == NULL) {
    return NULL;
  }
  return msg_.get()->c_str();
}

int main() {
  error nomem("Cannot allocate %d bytes", 100);
  if (nomem) {
    printf("An error: %s\n", nomem.msg());
  }
  error e = nomem;
  if (e) {
    printf("The same error: %s\n", e.msg());
  }
}

@reyoung reyoung force-pushed the feature/ErrorHandlingInPaddle branch from 02830cf to 5a15c70 Compare January 18, 2017 13:02
@reyoung
Copy link
Collaborator Author

reyoung commented Jan 18, 2017

@wangkuiyi Follow comments, except use Error as class name.

* use log(FATAL) or CHECK to make program exit before. When we clean all
* log(FATAL) and CHECK in Paddle, 'check' method will be removed.
*/
class Error final {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不需要final吧?有些C++程序员不知道final。而且这里也非必须final。

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

/**
* Default Status. OK
*/
inline Error() {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里不需要inline。如果是为了优化编译性能,尽量让编译器去决定性能吧。

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

/**
* @brief Create an Error use printf syntax.
*/
inline explicit Error(const char* fmt, ...) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里不需要inline。

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

class Error final {
public:
/**
* Default Status. OK
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个comment写得是不是太简洁了?下面这样?

@brief Construct an no-error value.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

va_start(ap, fmt);
constexpr size_t kBufferSize = 1024;
this->errMsg_.reset(new std::string(kBufferSize, 0));
auto sz = vsnprintf(&(*errMsg_)[0], kBufferSize, fmt, ap);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这样写暴露了string的太多实现细节了——string里有一个连续的buffer。没有必要。我之前那样写更简短。

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

/**
* @brief what will return the error message. If no error, return nullptr.
*/
inline const char* msg() const {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不需要inline

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

inline void check() const { CHECK(*this) << msg(); }

private:
std::shared_ptr<std::string> errMsg_;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

class已经叫error了,这里只需要叫msg_。

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

* @note It is a temp method used during cleaning Paddle code. It will be
* removed later.
*/
inline void check() const { CHECK(*this) << msg(); }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error是新加入的代码,为什么要等着“remove later”?应该一开始就不要制造需要remove的问题吧?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

因为Paddle内部充斥着LOG(Fatal)和CHECK。我们不太可能在一个PR里面,把这些东西全部替换掉。。只能逐层的一步一步来,先替换掉内层的CHECK和LOG(FATAL),返回给外层。

但是,这样一步一步来的时候,在外层处理这个Error就需要还调用CHECK。这里这个函数就是方便外层去检查这个Error的。

最终,我们的Paddle会将这个Error,全部返回给最上层的调用(例如Main函数或者Trainer中),这样我们就可以把这个check去掉,然后在一个地方写这个CHECK了。

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

理解了。好的。

*/
inline const char* msg() const {
if (errMsg_) {
return errMsg_->data();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里应该用c_str,不可以用 data: http://stackoverflow.com/questions/194634/string-c-str-vs-data

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

/**
* @brief operator bool, return True if there is no error.
*/
inline operator bool() const { return !errMsg_; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

因为msg调用的c_str可能运行代价比较高(如果string内部使用的不是连续buffer),所以这里应该延承 error::error() 的 syntax,用 msg_.get() != NULL 作为判断标准。

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里实际上调用的是shared_ptr是不是空指针。

Copy link
Collaborator

@wangkuiyi wangkuiyi Jan 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我明白了。

不过这是在我知道C++里可以重载operator (),并且去 文档 里查阅,确定了shared_ptr重载了operator () 的情况下,才能理解这个用法。如果我不查阅文档,我甚至不确定 shared_ptr 重载的 operator () 是返回的 bool 类型还是 pointer 类型。

所以我建议这里还是用朴素的写法:msg_.get() != NULL

我们的原则是希望源码的读者(维护着)具备最少的知识,就能理解我们的代码。

@wangkuiyi
Copy link
Collaborator

wangkuiyi commented Jan 18, 2017

我之前贴的那个例子,没有办法构造“no error”。受到 @reyoung 的修改的启发,更新一下我的那个例子:

#include <stdarg.h>

#include <string>

// We try to make error looks like a native POD type, so we broke the
// naming convention for C++ classes.
//
// The following example shows supported syntax of error:
/*
  error noerr;                                   // 1. ctor of no error.
  error nomem("Cannot allocate %d bytes", 100);  // 2. ctor is like printf.
  if (nomem) {                                   // 3. type conversion to bool.
    printf("An error: %s", nomem.msg());         // 4. retrieve message.
  }
  error e = nomem;                               // 5. feel free to copy.
 */
class error {
public:
  error() {}
  error(const char* format, ...);
  operator bool () const { return msg_.get() != NULL; }
  const char* msg() const;

private:
  std::shared_ptr<std::string> msg_;
};

error::error(const char* format, ...) {
  char buffer[256];
  va_list ap;
  va_start(ap, format);
  vsnprintf(buffer, 256, format, ap);
  va_end(ap);
  msg_.reset(new std::string(buffer));
}

const char* error::msg() const {
  if (msg_.get() == NULL) {
    return NULL;
  }
  return msg_.get()->c_str();
}

以下代码适合改成unit test,放在 error_test.cc里:

int main() {
  error noerr;
  if (noerr) {
    printf("We should never see this!");
  }
  
  error nomem("Error");
  if (nomem) {
    printf("An error: %s\n", nomem.msg());
  }
}

@reyoung reyoung force-pushed the feature/ErrorHandlingInPaddle branch from 4bb9eb7 to f3739dc Compare January 19, 2017 02:09
@reyoung reyoung force-pushed the feature/ErrorHandlingInPaddle branch from f3739dc to c88dec2 Compare January 19, 2017 02:10
@reyoung reyoung merged commit 7f0ad62 into PaddlePaddle:develop Jan 19, 2017
@reyoung reyoung deleted the feature/ErrorHandlingInPaddle branch February 24, 2017 06:41
zhhsplendid pushed a commit to zhhsplendid/Paddle that referenced this pull request Sep 25, 2019
* update install doc for release 1.5.2, test=develop

* update Tables.md for 1.5.2, test=develop

* update Tables.md to 1.5.1 for windows, test=develop
wangxicoding pushed a commit to wangxicoding/Paddle that referenced this pull request Dec 9, 2021
lizexu123 pushed a commit to lizexu123/Paddle that referenced this pull request Feb 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants