前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >实现一个strong_rc_ptr(非线程安全版本的std::shared_ptr)

实现一个strong_rc_ptr(非线程安全版本的std::shared_ptr)

作者头像
owent
发布于 2024-10-09 06:32:50
发布于 2024-10-09 06:32:50
18100
代码可运行
举报
文章被收录于专栏:owentowent
运行总次数:0
代码可运行

背景

我们的新项目有个比较复杂的全区全服交易行系统,其中搜索和推荐是高实时性全区服多维度排序的,并且要支持比较复杂的标签交集查询和属性范围查询的自由组合。 当有订单发生变化时,它不仅仅会影响全服状态下搜索和推荐条件的结果变化,也会同时影响商品维度的聚合,交易行层面的数据聚合。

为了降低搜索开销和提供弹性伸缩的能力,我们在多个不同类型的服务上设计了基于视图的动态索引支持,并且针对常用的一些搜索推荐模型,实现了大量的静态索引。

这也同时带来的影响是每当订单有变化时它都有可能会去刷新大量的索引。我们早期第一版直接用 std::shared_ptr 来维护订单信息。每次变更索引时都是重新入删除和插入一个 std::shared_ptrstd::shared_ptr 底层的实现是使用 std::atomic 来维护了引用计数。每次变更操作都会导致 std::atomic 增减,同时带来 CPU Cache Line 失效。

在大多数场景下,这也没太大问题,因为大多数场景下参数传递 std::shared_ptr 也好,内存存放 std::shared_ptr 也好,通常上下文会存在更多逻辑操作,导致这个CPU Cache Line 失效后重新prefetch的开销占比很小。但是在我们这个场景下情况又有所不同。 我们采用了BTree来管理有序索引,这样比常规的红黑树来说内存结构会更紧凑,缓存命中率更高。然而在连续大量索引发生变化时,一直要发生Cache失效->重新prefetch,Cache失效->重新prefetch的过程。 这反而带来了额外的浪费。我们在后来用valgrind分析的过程中也确实验证导这部分的Cache Miss率明显高于其他操作。于是实现一个非线程安全版本的 shared_ptr 就被提上了日程。思路其实和 Rust的 std::rc::Rc 很像。

其实GCC的 STL本身自带费线程安全的内部版本的,可以通过使用 template<class T> using strong_rc_ptr<T> = std::__shared_ptr<T, std::_S_single>; 然后使用 strong_rc_ptr<T> 就是单线程模式。然而我们这里考虑到跨编译器跨平台,所以自己实现了一个。

实现

基础功能

引用计数型的智能指针的基本原理比较简单。就是有一个存储区去存放引用计数。当计数清零后释放。

为了加快解引用的性能,原始指针并没有放在引用计数的存储区里,而是直接放在智能指针对象上。这样大多数场景访问指针内容的时候不需要多一次跳转去查询实际地址。

接下来更多的代码其实是在适配和优化各种使用场景。 首先是针对各类构造场景,我的实现分成了5种。( std::shared_ptrboost::shared_ptr 分别按各自的实现有一些实现合并和分离,和我这边实现稍有差异,但主体Feature相同 )

  • 默认构造函数:接收指针传入,通过 new/delete 管理计数对象。
  • 默认inplace构造:通过 new/delete 管理计数对象和实际对象的内存,通过placement new构造对象。(碎片更少, make_shared/make_strong_rc 的构造方式)
  • 默认带自定义Deletor的构造:比上面的构造多存储一个Deletor对象,通过 new/delete 管理计数对象。
  • 指定Allocator的inplace构造:通过自定义Allocator管理计数对象和实际对象的内存,通过placement new构造对象。( 自定义分配器, allocate_shared/allocate_strong_rc 的构造方式)boost::shared_ptr 的实现有问题,某些地方显示使用 new/delete 操作符了,导致对自定义Allocator没有完整的支持。
  • 指定Allocator的带自定义Deletor的构造:比上面的构造多存储一个Deletor对象,自定义Allocator管理计数对象。

有些实现里带Deletor和带Allocator的实现是合并的,我这里分离出来了。是因为没有Deletor的时候可以少一哥对象的维护操作,理论上会更利于编译优化。

另外 boost::shared_ptr 的某些分支实现显示使用 new/delete 操作符来分配管理对象的内存结构了,没有使用 allocator_traits<Alloc>::rebind_alloc<T> 这种形式。实际上也是不对的。 所以这里我采用和 std::shared_ptr 一样的方式保持对自定义Allocator的完全兼容。具体详情也可以参考我之前写的 《手夯一个STL allocator和对象内存分析组件》的Allocator rebind章节

enable_shared_from_this 的两种实现

接下来是 enable_shared_from_this 的实现。要实现继承 enable_shared_from_this 的对象自动带 share_from_this() 接口,首先基类需要记录一个 weak_ptr/weak_rc_ptr 。 然后构造 shared_ptr 的时候检测目标指针可否转换成 enable_shared_from_this 指针。这个检测目前新版本的编译器和STL实现大多数采用 std::void_t<T> 转换的detection idiom来实现。 比如GCC实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private:
  template<typename _Tp1>
  void _M_weak_assign(_Tp1* __p, const __shared_count<_Lp>& __n) const noexcept {
    _M_weak_this._M_assign(__p, __n);
  }

  friend const __enable_shared_from_this*  __enable_shared_from_this_base(
    const __shared_count<_Lp>&, const __enable_shared_from_this* __p) {
    return __p;
  }

  // ...
  template<typename _Yp>
  using __esft_base_t = decltype(__enable_shared_from_this_base(
      std::declval<const __shared_count<_Lp>&>(),
      std::declval<_Yp*>()));

  // Detect an accessible and unambiguous enable_shared_from_this base.
  template<typename _Yp, typename = void>
  struct __has_esft_base : false_type { };

  template<typename _Yp>
  struct __has_esft_base<_Yp, __void_t<__esft_base_t<_Yp>>> : __not_<is_array<_Tp>> { }; // No enable shared_from_this for arrays

  template<typename _Yp, typename _Yp2 = typename remove_cv<_Yp>::type>
  typename enable_if<__has_esft_base<_Yp2>::value>::type
  _M_enable_shared_from_this_with(_Yp* __p) noexcept {
    if (auto __base = __enable_shared_from_this_base(_M_refcount, __p))
      __base->_M_weak_assign(const_cast<_Yp2*>(__p), _M_refcount);
  }

  template<typename _Yp, typename _Yp2 = typename remove_cv<_Yp>::type>
  typename enable_if<!__has_esft_base<_Yp2>::value>::type
  _M_enable_shared_from_this_with(_Yp*) noexcept { }

MSVC的STL也是这个实现,但是这个idiom在C++17之后一些行为才被标准化,老的编译器可能会不支持。所以我这里还是采用了更传统的实现方法,即采用模板匹配比动态参数函数更优先匹配的机制。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template <class T1, class T2, class T3>
inline void __enable_shared_from_this_with(const __strong_rc_counter<T1>* __n,
                                                                          const T2* __py,
                                                                          const enable_shared_rc_from_this<T3>* __p) {
  if (nullptr != __p) {
    __p->__internal_weak_assign(const_cast<T2*>(__py), *__n);
  }
}

template <class T1, class T2, class T3, size_t T3SIZE>
inline void __enable_shared_from_this_with(
    const __strong_rc_counter<T1>* __n, const T2* __py, const enable_shared_rc_from_this<T3[T3SIZE]>* __p) {
  if (nullptr != __p) {
    for (auto& p : *__p) {
      p->__internal_weak_assign(const_cast<T2*>(__py), *__n);
    }
  }
}

#ifdef _MANAGED

// Avoid C4793, ... causes native code generation

struct __sp_any_pointer {
  template <class T>
  __sp_any_pointer(T*) {}  // NOLINT: runtime/explicit
};

inline void __enable_shared_from_this_with(__sp_any_pointer, __sp_any_pointer,
                                                                          __sp_any_pointer) {}

#else  // _MANAGED

inline void __enable_shared_from_this_with(...) {}

#endif  // _MANAGED

const类型比较操作符

在实现操作符重载的时候有个小tips需要注意。比如如果比较 shared_ptr<T>shared_ptr<const T> 的时候。 如果实现的不好,容易在某些SFINAE流程里推断成 T* ,然后由于 const T* 不能转成 T* 导致推断失败。 这个需要稍微注意一下。

std::shared_ptrboost::shared_ptr 的差异

在写单元测试的时候,我发现 std::shared_ptrboost::shared_ptr 的实现上还有一些行为上的差异。虽然说这部分可能是未定义行为,但是我觉得 std::shared_ptr 的行为大部分场景下更符合直觉。

空指针引用计数
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// boost::shared_ptr
class shared_count
{
private:
  sp_counted_base * pi_;

public:
  BOOST_CONSTEXPR shared_count() BOOST_SP_NOEXCEPT: pi_(0)
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
      , id_(shared_count_id)
#endif
  {
  }

  long use_count() const BOOST_SP_NOEXCEPT {
    return pi_ != 0? pi_->use_count(): 0;
  }
};

// GCC: std::shared_ptr
template<_Lock_policy _Lp = __default_lock_policy>
class _Sp_counted_base : public _Mutex_base<_Lp> {
public:
  _Sp_counted_base() noexcept : _M_use_count(1), _M_weak_count(1) { }

  long _M_get_use_count() const noexcept {
    // No memory barrier is used here so there is no synchronization
    // with other threads.
    return __atomic_load_n(&_M_use_count, __ATOMIC_RELAXED);
  }
};

// 单元测试
util::memory::strong_rc_ptr<int> pi;
// boost::shared_ptr 行为(strong_rc_ptr采用此行为)
CASE_EXPECT_TRUE(pi.use_count() == 0);
// std::shared_ptr 行为
CASE_EXPECT_TRUE(pi.use_count() == 1);

pi.reset(static_cast<int *>(nullptr));
CASE_EXPECT_TRUE(pi.use_count() == 1);

这里空指针 boost::shared_ptr 引用计数为0, std::shared_ptr 引用计数为1。我觉得0更符合直觉就按0处理了。

操作符的怪异行为
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/// GCC: libstdc++
template<typename _Tp1, typename _Tp2, _Lock_policy _Lp>
inline bool operator==(const __shared_ptr<_Tp1, _Lp>& __a, const __shared_ptr<_Tp2, _Lp>& __b) noexcept {
  return __a.get() == __b.get();
}
#ifdef __cpp_lib_three_way_comparison
template<typename _Tp, typename _Up, _Lock_policy _Lp>
inline strong_ordering operator<=>(const __shared_ptr<_Tp, _Lp>& __a, const __shared_ptr<_Up, _Lp>& __b) noexcept {
  return compare_three_way()(__a.get(), __b.get());
}
// 其他相似的重载不再展示 ...
#else
template<typename _Tp, typename _Up, _Lock_policy _Lp>
inline bool operator<(const __shared_ptr<_Tp, _Lp>& __a, const __shared_ptr<_Up, _Lp>& __b) noexcept {
  using _Tp_elt = typename __shared_ptr<_Tp, _Lp>::element_type;
  using _Up_elt = typename __shared_ptr<_Up, _Lp>::element_type;
  using _Vp = typename common_type<_Tp_elt*, _Up_elt*>::type;
  return less<_Vp>()(__a.get(), __b.get());
}
// 其他相似的重载不再展示 ...
#endif

// boost::shared_ptr
template<class T, class U>
inline bool operator==(shared_ptr<T> const & a, shared_ptr<U> const & b) BOOST_SP_NOEXCEPT {
  return a.get() == b.get();
}

template<class T, class U>
inline bool operator<(shared_ptr<T> const & a, shared_ptr<U> const & b) BOOST_SP_NOEXCEPT {
    return a.owner_before(b);
}

// 单元测试
{
  util::memory::strong_rc_ptr<int> p1;
  util::memory::strong_rc_ptr<int> p2;
  p2.reset(nullptr);

  CASE_EXPECT_TRUE(p1 == p2);

  // std::shared_ptr 行为(strong_rc_ptr采用此行为)
  CASE_EXPECT_FALSE((p1 < p2 || p2 < p1));
  // boost::shared_ptr 行为
  CASE_EXPECT_TRUE((p1 < p2 || p2 < p1));
}

这里可以看到 ,当 p1 == p2 的时候直观上显然 p1 < p2 || p2 < p1 应该是 false 的。但是因为boost的实现 operator== 用了原始指针,但是 operator< 缺用的管理数据块的指针。两个不一致。 这里 strong_rc_ptr 保持和 std::shared_ptr 的行为一致。

继承和父子转换和比较操作符
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct X {
  int dummy;
};

struct Y {
  int dummy2;
};

struct Z : public X, public virtual Y {};

// 单元测试
util::memory::strong_rc_ptr<Z> pz(new Z);
util::memory::strong_rc_ptr<X> px(pz);

CASE_EXPECT_TRUE(px.get() == pz.get());
CASE_EXPECT_TRUE(px == pz);

util::memory::strong_rc_ptr<Y> py(pz);

CASE_EXPECT_TRUE(py.get() == pz.get());
CASE_EXPECT_TRUE(py == pz);
CASE_EXPECT_FALSE(py < pz || pz < py);

// strong_rc_ptr 行为, std::shared_ptr 不允许编译通过
CASE_EXPECT_TRUE(px < py || py < px);
// boost::shared_ptr 行为,和下面操作符实现相关
CASE_EXPECT_FALSE(px < py || py < px);

util::memory::strong_rc_ptr<void> pvx(px);
util::memory::strong_rc_ptr<void> pvy(py);
util::memory::strong_rc_ptr<void> pvz(pz);

CASE_EXPECT_TRUE(pvx.get() != pvy.get());
CASE_EXPECT_TRUE(pvx != pvy);

// std::shared_ptr 行为(strong_rc_ptr采用此行为)
CASE_EXPECT_TRUE(pvx < pvy || pvy < pvx);
CASE_EXPECT_TRUE(pvy < pvz || pvz < pvy);

// boost::shared_ptr 行为,和下面操作符实现相关
CASE_EXPECT_FALSE(pvx < pvy || pvy < pvx);
CASE_EXPECT_FALSE(pvy < pvz || pvz < pvy);

对于父子类转换后再类型擦除后的比较操作。我也是觉得 std::shared_ptr 的行为更符合直觉,所以按 std::shared_ptr 的行为为准。

单元测试

单元测试我直接就扒了 boost.shared_ptr 的了。部分和 std::shared_ptr 差异的部分按 std::shared_ptr 的行为做了调整。 这样覆盖场景应该是比较完善了。

周边组件迁移

一键切换组件

因为我这个 strong_rc_ptr 实现了 shared_ptr 的所有接口,所以替换起来非常简单。也提供了一套 traits 接口用于一键切换。这样也方便比较性能差异。 代码大致上这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
LIBATFRAME_UTILS_NAMESPACE_BEGIN
namespace memory {
enum class compat_strong_ptr_mode : int8_t {
  kStrongRc = 0,
  kStl = 1,
};

template <compat_strong_ptr_mode>
struct LIBATFRAME_UTILS_API_HEAD_ONLY compat_strong_ptr_function_trait;

template <>
struct LIBATFRAME_UTILS_API_HEAD_ONLY compat_strong_ptr_function_trait<compat_strong_ptr_mode::kStrongRc> {
  template <class Y>
  using shared_ptr = memory::strong_rc_ptr<Y>;

  template <class Y>
  using weak_ptr = memory::weak_rc_ptr<Y>;

  template <class Y>
  using enable_shared_from_this = memory::enable_shared_rc_from_this<Y>;

  template <class Y, class... ArgsT>
  static inline memory::strong_rc_ptr<Y> make_shared(ArgsT&&... args) {
    return memory::make_strong_rc<Y>(std::forward<ArgsT>(args)...);
  }

  template <class Y, class Alloc, class... TArgs>
  static inline memory::strong_rc_ptr<Y> allocate_shared(const Alloc& alloc, TArgs&&... args) {
    return memory::allocate_strong_rc<Y>(alloc, std::forward<TArgs>(args)...);
  }

  template <class Y, class F>
  static inline memory::strong_rc_ptr<Y> static_pointer_cast(F&& f) {
    return memory::static_pointer_cast<Y>(std::forward<F>(f));
  }

  template <class Y, class F>
  static inline memory::strong_rc_ptr<Y> const_pointer_cast(F&& f) {
    return memory::const_pointer_cast<Y>(std::forward<F>(f));
  }

#if defined(LIBATFRAME_UTILS_ENABLE_RTTI) && LIBATFRAME_UTILS_ENABLE_RTTI
  template <class Y, class F>
  static inline memory::strong_rc_ptr<Y> dynamic_pointer_cast(F&& f) {
    return memory::dynamic_pointer_cast<Y>(std::forward<F>(f));
  }
#endif
};

template <>
struct LIBATFRAME_UTILS_API_HEAD_ONLY compat_strong_ptr_function_trait<compat_strong_ptr_mode::kStl> {
  template <class Y>
  using shared_ptr = std::shared_ptr<Y>;

  template <class Y>
  using weak_ptr = std::weak_ptr<Y>;

  template <class Y>
  using enable_shared_from_this = std::enable_shared_from_this<Y>;

  template <class Y, class... ArgsT>
  static inline std::shared_ptr<Y> make_shared(ArgsT&&... args) {
    return std::make_shared<Y>(std::forward<ArgsT>(args)...);
  }

  template <class Y, class Alloc, class... TArgs>
  static inline std::shared_ptr<Y> allocate_shared(const Alloc& alloc, TArgs&&... args) {
    return std::allocate_shared<Y>(alloc, std::forward<TArgs>(args)...);
  }

  template <class Y, class F>
  static inline std::shared_ptr<Y> static_pointer_cast(F&& f) {
    return std::static_pointer_cast<Y>(std::forward<F>(f));
  }

  template <class Y, class F>
  static inline std::shared_ptr<Y> const_pointer_cast(F&& f) {
    return std::const_pointer_cast<Y>(std::forward<F>(f));
  }

#if defined(LIBATFRAME_UTILS_ENABLE_RTTI) && LIBATFRAME_UTILS_ENABLE_RTTI
  template <class Y, class F>
  static inline std::shared_ptr<Y> dynamic_pointer_cast(F&& f) {
    return std::dynamic_pointer_cast<Y>(std::forward<F>(f));
  }
#endif
};

template <class T, compat_strong_ptr_mode PtrMode>
struct LIBATFRAME_UTILS_API_HEAD_ONLY compat_strong_ptr_type_trait {
  using shared_ptr = typename compat_strong_ptr_function_trait<PtrMode>::template shared_ptr<T>;
  using weak_ptr = typename compat_strong_ptr_function_trait<PtrMode>::template weak_ptr<T>;
};

}  // namespace memory
LIBATFRAME_UTILS_NAMESPACE_END

算法和容器基础组件适配

有了上面的一键切换traits,其他的组件切换也就比较简单了。在 atframe_utils 内的组件接入一键切换的有时间轮定时器实现 jeffies_timer , LRU算法容器 lru_map ,分布式系统和分布式事务的 WAL模块

还有一部分是我们工程里有很多读取策划通过配置Excel转出的数据,这部分访问量也很大。所以我对这个生成读表代码的模板系统 xres-code-generator 也做了改造支持。

效果

其实在完成之前,其实我们也不确定到底能带来多大的提升。简单的循环benchmark其实也没啥意义,因为和实际使用场景的访问情况相差太远了。

我们在完成之后对我们实际项目路14-16个静态索引的交易行上下架请求和搜索的场景做了对比(不包含Excel读表改造),大概比 std::shared_ptr 提升了10%-16%的综合性能,这里面其实附带了其他的一些视图和索引的比较操作和其他RPC的操作。所以单单这个智能指针的部分提升应该是更大的。

未来规划

之前实现 libcopp 对C++20协程支持的时候也有几处内部生命周期引用的地方是计划中后续改成无Cache Miss的版本的,后续看有空也改造一下吧。也是能减少一些不必要的内部开销。

我们也在继续逐渐把一些本来也不是线程安全的模块都换成新智能指针,欢迎有兴趣的小伙伴互相交流研究。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-10-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
决策树算法:ID3,C4.5,CART
对于基本树我将大致从以下四个方面介绍每一个算法:思想、划分标准、剪枝策略,优缺点。
zhangjiqun
2024/12/14
2570
决策树算法:ID3,C4.5,CART
决策树与随机森林(从入门到精通)[通俗易懂]
决策树(decision tree)是一种基本的分类与回归方法,本文主要讨论用于分类的决策树。决策树学习通常包括三个步骤:特征选择,决策树的生成和决策树的修剪。而随机森林则是由多个决策树所构成的一种分类器,更准确的说,随机森林是由多个弱分类器组合形成的强分类器。
全栈程序员站长
2022/08/01
8040
决策树与随机森林(从入门到精通)[通俗易懂]
算法金 | 决策树、随机森林、bagging、boosting、Adaboost、GBDT、XGBoost 算法大全
决策树是一种简单直观的机器学习算法,它广泛应用于分类和回归问题中。它的核心思想是将复杂的决策过程分解成一系列简单的决策,通过不断地将数据集分割成更小的子集来进行预测。本文将带你详细了解决策树系列算法的定义、原理、构建方法、剪枝与优化技术,以及它的优缺点。
算法金
2024/06/25
6510
算法金 | 决策树、随机森林、bagging、boosting、Adaboost、GBDT、XGBoost 算法大全
关于XGBoost、GBDT、Lightgbm的17个问题
9.lightgbm和xgboost有什么区别?他们的loss一样么?算法层面有什么区别?
Ai学习的老章
2019/10/22
5.3K0
关于XGBoost、GBDT、Lightgbm的17个问题
从决策树到XGBOOST
XGBoost在机器学习领域可谓风光无限,作为从学术界来的模范生,帮助工业界解决了许多实际问题,真可谓:
西西木木
2020/06/07
1.6K0
一文详尽XGBOOST的前世今生
XGBOOST:简单来说是集成了很多个基学习器(如Cart决策树)的模型。它是集成学习的串行方式(boosting)的一种经典实现,是广泛应用在工业、竞赛上的一大神器。
算法进阶
2022/06/01
9600
一文详尽XGBOOST的前世今生
从决策树到GBDT梯度提升决策树和XGBoost
决策树可以转换成if-then规则的集合,也可以看作是定义在特征空间划分类的条件概率分布。决策树学习算法包括三部分:特征选择,数的生成和数的剪枝。最大优点: 可以自学习。在学习的过程中,不需要使用者了解过多背景知识,只需要对训练实例进行较好的标注,就能够进行学习。显然,属于有监督学习。 常用有一下三种算法:
大鹅
2021/06/15
1.2K0
从决策树到GBDT梯度提升决策树和XGBoost
数据挖掘算法(logistic回归,随机森林,GBDT和xgboost)
面网易数据挖掘工程师岗位,第一次面数据挖掘的岗位,只想着能够去多准备一些,体验面这个岗位的感觉,虽然最好心有不甘告终,不过继续加油。 不过总的来看,面试前有准备永远比你没有准备要强好几倍。 因为面试过程看重的不仅是你的实习经历多久怎样,更多的是看重你对基础知识的掌握(即学习能力和逻辑),实际项目中解决问题的能力(做了什么贡献)。 ---- 先提一下奥卡姆剃刀:给定两个具有相同泛化误差的模型,较简单的模型比较复杂的模型更可取。以免模型过于复杂,出现过拟合的问题。 如果你想面数据挖掘岗必须先了解下面这部分的基本
机器学习AI算法工程
2018/03/14
3.4K0
数据挖掘算法(logistic回归,随机森林,GBDT和xgboost)
决策树学习笔记(一):特征选择
相信很多朋友已经对决策树很熟悉了,决策树是机器学习中的一种基本的可用于分类与回归的方法,它是一些集成学习如GBDT,XGboost等复杂模型的基础。这些高级模型比如XGboost可以非常好地拟合数据,在数据挖掘比赛以及工业界中都有着非常出色的表现,受到了无数爱好者的追捧。
1480
2019/07/14
1.8K0
理论:决策树及衍射指标
特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下的经验条件熵H(D|A)之差
sladesal
2018/08/27
3460
理论:决策树及衍射指标
决策树与随机森林
首先,在了解树模型之前,自然想到树模型和线性模型有什么区别呢?其中最重要的是,树形模型是一个一个特征进行处理,之前线性模型是所有特征给予权重相加得到一个新的值。决策树与逻辑回归的分类区别也在于此,逻辑回归是将所有特征变换为概率后,通过大于某一概率阈值的划分为一类,小于某一概率阈值的为另一类;而决策树是对每一个特征做一个划分。另外逻辑回归只能找到线性分割(输入特征x与logit之间是线性的,除非对x进行多维映射),而决策树可以找到非线性分割。
西西木木
2020/06/02
1.3K0
决策树与随机森林
博客 | 干货 | 一文读懂横扫Kaggle的XGBoost原理与实战(一)
首先说一下,大家的催更我都有看到,无奈我请假出差了,预计十来天,这期间也会尽力更新文章,感谢大家的支持。今天发一篇北大18级硕士Jason Cai关于xgboost的文章,后续还有相关内容的进阶。首先说一下,xgboost也算是集成学习的一种。正文如下:
AI研习社
2018/12/26
1.1K1
RF(随机森林)、GBDT、XGBoost面试级整理
由于本文是基于面试整理,因此不会过多的关注公式和推导,如果希望详细了解算法内容,敬请期待后文。   RF、GBDT和XGBoost都属于集成学习(Ensemble Learning),集成学习的目的是通过结合多个基学习器的预测结果来改善单个学习器的泛化能力和鲁棒性。   根据个体学习器的生成方式,目前的集成学习方法大致分为两大类:即个体学习器之间存在强依赖关系、必须串行生成的序列化方法,以及个体学习器间不存在强依赖关系、可同时生成的并行化方法;前者的代表就是Boosting,后者的代表是Bagg
机器学习AI算法工程
2018/03/15
6.3K0
【机器学习】——决策树以及随机森林
前言:决策树算法(Decision Tree)详解 决策树(DecisionTree)是一种基于树形结构的监督学习算法,广泛应用于分类和回归任务。它通过一系列的决策规则逐步将数据集划分成多个子集,从而构建出易于理解的决策模型。决策树不仅易于可视化、便于解释,还能够处理复杂的多变量决策问题,因此在各类机器学习模型中占有重要地位。
用户11286421
2024/09/29
1.8K0
【机器学习】——决策树以及随机森林
认真的聊一聊决策树和随机森林
多棵决策树组成了一片“森林”,计算时由每棵树投票或取均值的方式来决定最终结果,体现了三个臭皮匠顶个诸葛亮的中国传统民间智慧。
肉眼品世界
2021/03/09
1.2K0
认真的聊一聊决策树和随机森林
机器学习7:集成学习--XGBoost
对于XGBoost算法原理看陈天奇的PPT和一份算法实战指导文档就够了(文末附网盘链接)。
用户5473628
2019/08/08
1.5K0
机器学习7:集成学习--XGBoost
面试、笔试题集:集成学习,树模型,Random Forests,GBDT,XGBoost
分类和回归树(简称 CART)是 Leo Breiman 引入的术语,指用来解决分类或回归预测建模问题的决策树算法。它常使用 scikit 生成并实现决策树: sklearn.tree.DecisionTreeClassifier 和 sklearn.tree.DecisionTreeRegressor 分别构建分类和回归树。
流川疯
2022/05/10
1K0
面试、笔试题集:集成学习,树模型,Random Forests,GBDT,XGBoost
【机器学习】--决策树和随机森林
决策树是一种非线性有监督分类模型,随机森林是一种非线性有监督分类模型。线性分类模型比如说逻辑回归,可能会存在不可分问题,但是非线性分类就不存在。 二、具体原理
LhWorld哥陪你聊算法
2018/09/13
9860
【机器学习】--决策树和随机森林
最全!两万字带你完整掌握八大决策树!
决策树是一个非常常见并且优秀的机器学习算法,它易于理解、可解释性强,其可作为分类算法,也可用于回归模型。
AI科技评论
2020/07/08
2K0
随机森林
在机器学习的分类中,集成学习是按照学习方式分类的一种机器学习,所以先从集成学习讲起。
章鱼carl
2022/03/31
5090
随机森林
推荐阅读
相关推荐
决策树算法:ID3,C4.5,CART
更多 >
LV.2
这个人很懒,什么都没有留下~
目录
  • 背景
  • 实现
    • 基础功能
    • enable_shared_from_this 的两种实现
    • const类型比较操作符
    • std::shared_ptr 和 boost::shared_ptr 的差异
      • 空指针引用计数
      • 操作符的怪异行为
      • 继承和父子转换和比较操作符
  • 单元测试
  • 周边组件迁移
    • 一键切换组件
    • 算法和容器基础组件适配
  • 效果
  • 未来规划
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档