当前位置: 首页 > news >正文

PyTorch中的intrusive_ptr

PyTorch中的intrusive_ptr

前言

intrusive_ptrunique_ptrshared_ptr等一樣,都是smart pointer。但是intrusive_ptr比較特別,它所指向的物件類型必須繼承自intrusive_ptr_target,而intrusive_ptr_target必須實現引用計數相關的函數才行。

在PyTorch中,StorageImpl繼承自c10::intrusive_ptr_target,所以c10::intrusive_ptr可以與StorageImpl搭配使用。

同樣地,TensorImpl也繼承自c10::intrusive_ptr_target,而TensorBase就是透過 c10::intrusive_ptr<TensorImpl, UndefinedTensorImpl> impl_;這個成員變數來存取TensorImpl物件的。

想要循環引用時,如果使用shared_ptr會出現無法析構的問題,我們可以使用weak_ptr來解決。weak_ptr不會增加所指向物件的引用計數,所以從引用計數的角度來看,就不會有deadlock的問題。注意weak_ptr必須搭配shared_ptr來使用。

想要有多個指標指向同一物件時,如果使用shared_ptr會出現重複析構的問題。使用shared_ptr的話,引用計數是儲存在shared_ptr裡;使用intrusive_ptr的話,引用計數是儲存在指向的物件裡,一個物件只有一個引用計數,所以不會有重複析構的問題。

intrusive_ptr的缺點是無法使用weak_ptr,所以不能用在循環引用的場景中。

【C++11新特性】 C++11智能指针之weak_ptr

boost::intrusive_ptr原理介绍

Smart Ptr 一點訣 (1):使用 intrusive_ptr

c10::intrusive_ptr_target

c10/util/intrusive_ptr.h

提供引用計數功能的base class(基類):

class C10_API intrusive_ptr_target {// Note [Weak references for intrusive refcounting]// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// Here's the scheme:////  - refcount == number of strong references to the object//    weakcount == number of weak references to the object,//      plus one more if refcount > 0//    An invariant: refcount > 0  =>  weakcount > 0////  - c10::StorageImpl stays live as long as there are any strong//    or weak pointers to it (weakcount > 0, since strong//    references count as a +1 to weakcount)////  - finalizers are called and data_ptr is deallocated when refcount == 0////  - Once refcount == 0, it can never again be > 0 (the transition//    from > 0 to == 0 is monotonic)////  - When you access c10::StorageImpl via a weak pointer, you must//    atomically increment the use count, if it is greater than 0.//    If it is not, you must report that the storage is dead.//mutable std::atomic<size_t> refcount_;mutable std::atomic<size_t> weakcount_;// ...
};

boost::instrusive_ptr在循環引用時會有無法析構的問題,PyTorch中的intrusive_ptr為了避免出現這種情況,被設計成兼具intrusive_ptrweak_ptr的功能,所以除了refcount_外,還有weakcount_成員變數。

constructors

  constexpr intrusive_ptr_target() noexcept : refcount_(0), weakcount_(0) {}// intrusive_ptr_target supports copy and move: but refcount and weakcount// don't participate (since they are intrinsic properties of the memory// location)intrusive_ptr_target(intrusive_ptr_target&& /*other*/) noexcept: intrusive_ptr_target() {}intrusive_ptr_target& operator=(intrusive_ptr_target&& /*other*/) noexcept {return *this;}intrusive_ptr_target(const intrusive_ptr_target& /*other*/) noexcept: intrusive_ptr_target() {}intrusive_ptr_target& operator=(const intrusive_ptr_target& /*other*/) noexcept {return *this;}

destructor

  // protected destructor. We never want to destruct intrusive_ptr_target*// directly.virtual ~intrusive_ptr_target() {
// Disable -Wterminate and -Wexceptions so we're allowed to use assertions
// (i.e. throw exceptions) in a destructor.
// We also have to disable -Wunknown-warning-option and -Wpragmas, because
// some other compilers don't know about -Wterminate or -Wexceptions and
// will show a warning about unknown warning options otherwise.
#if defined(_MSC_VER) && !defined(__clang__)
#pragma warning(push)
#pragma warning( \disable : 4297) // function assumed not to throw an exception but does
#else
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wunknown-warning-option"
#pragma GCC diagnostic ignored "-Wterminate"
#pragma GCC diagnostic ignored "-Wexceptions"
#endifTORCH_INTERNAL_ASSERT_DEBUG_ONLY(// Second condition is there to accommodate// unsafe_adapt_non_heap_allocated: since we are doing our own// deallocation in that case, it is correct for each// expected_decref to have happened (some user code tried to// decref and thus free the object, but it didn't happen right// away) or not (no user code tried to free the object, and// now it's getting destroyed through whatever mechanism the// caller of unsafe_adapt_non_heap_allocated wanted to// use). We choose our reference count such that the count// will not dip below INT_MAX regardless.refcount_.load() == 0 || refcount_.load() >= INT_MAX,"Tried to destruct an intrusive_ptr_target that still has intrusive_ptr to it; refcount was ",refcount_.load());TORCH_INTERNAL_ASSERT_DEBUG_ONLY(// See ~intrusive_ptr for optimization that will frequently result in 1// at destruction time.weakcount_.load() == 1 || weakcount_.load() == 0 ||weakcount_.load() == INT_MAX - 1 || weakcount_.load() == INT_MAX,"Tried to destruct an intrusive_ptr_target that still has weak_intrusive_ptr to it");
#if defined(_MSC_VER) && !defined(__clang__)
#pragma warning(pop)
#else
#pragma GCC diagnostic pop
#endif}

c10::raw::intrusive_ptr::incref/decref

用於增加及減少引用計數的函數:

namespace c10 {
// ...namespace raw {namespace intrusive_ptr {// WARNING: Unlike the reclaim() API, it is NOT valid to pass
// NullType::singleton to this function
inline void incref(intrusive_ptr_target* self) {if (self) {detail::atomic_refcount_increment(self->refcount_);}
}// WARNING: Unlike the reclaim() API, it is NOT valid to pass
// NullType::singleton to this function
inline void decref(intrusive_ptr_target* self) {// Let it diec10::intrusive_ptr<intrusive_ptr_target>::reclaim(self);// NB: Caller still has 'self' pointer, but it's now invalid.// If you want more safety, used the actual c10::intrusive_ptr class
}// ...} // namespace intrusive_ptr// ...} // namespace raw} // namespace c10
class C10_API intrusive_ptr_target {// ...friend inline void raw::intrusive_ptr::incref(intrusive_ptr_target* self);// ...
};

(但為何decref不是friend function?)

decref的作用看起來是reset不是把ref count減一?

c10::intrusive_ptr::reclaim

  /*** Takes an owning pointer to TTarget* and creates an intrusive_ptr that takes* over ownership. That means the refcount is not increased.* This is the counter-part to intrusive_ptr::release() and the pointer* passed in *must* have been created using intrusive_ptr::release().*/static intrusive_ptr reclaim(TTarget* owning_ptr) {TORCH_INTERNAL_ASSERT_DEBUG_ONLY(owning_ptr == NullType::singleton() ||owning_ptr->refcount_.load() == 0 || owning_ptr->weakcount_.load(),"TTarget violates the invariant that refcount > 0  =>  weakcount > 0");return intrusive_ptr(owning_ptr, raw::DontIncreaseRefcount{});}

c10::StorageImpl

c10/core/StorageImpl.h

繼承自c10::intrusive_ptr_target的具體功能類:

struct C10_API StorageImpl : public c10::intrusive_ptr_target {//...
};

接下來看看c10::intrusive_ptr_target是怎麼與c10::intrusive_ptr搭配使用的。

使用案例一

at::detail::_empty_generic

aten/src/ATen/EmptyTensor.cpp

先來看看在aten/src/ATen/EmptyTensor.cppat::detail::_empty_generic函數中intrusive_ptr是如何被使用的:

  auto storage_impl = c10::make_intrusive<StorageImpl>(c10::StorageImpl::use_byte_size_t(),size_bytes,allocator,/*resizeable=*/true);

可以看到它呼叫了c10::make_intrusive,並以StorageImpl為模板參數,且傳入四個參數(這四個參數是StorageImpl建構子所需的)。

c10::make_intrusive

c10/util/intrusive_ptr.h

template <class TTarget,class NullType = detail::intrusive_target_default_null_type<TTarget>,class... Args>
inline intrusive_ptr<TTarget, NullType> make_intrusive(Args&&... args) {return intrusive_ptr<TTarget, NullType>::make(std::forward<Args>(args)...);
}

模板參數TTargetStorageImpl,傳入四個型別分別為use_byte_size_t, SymInt size_bytes, at::Allocator*, bool的參數。

c10::intrusive_ptr::make

c10/util/intrusive_ptr.h

  /*** Allocate a heap object with args and wrap it inside a intrusive_ptr and* incref. This is a helper function to let make_intrusive() access private* intrusive_ptr constructors.*/template <class... Args>static intrusive_ptr make(Args&&... args) {return intrusive_ptr(new TTarget(std::forward<Args>(args)...));}

模板參數TTargetStorageImpl,這裡會將四個型別分別為use_byte_size_t, SymInt size_bytes, at::Allocator*, bool的參數接力傳給TTarget建構子。

此處透過new TTarget得到StorageImpl物件指標後,會接著呼叫intrusive_ptr的建構子。

c10::intrusive_ptr constructor

c10/util/intrusive_ptr.h

  // raw pointer constructors are not public because we shouldn't make// intrusive_ptr out of raw pointers except from inside the make_intrusive(),// reclaim() and weak_intrusive_ptr::lock() implementations.// This constructor will increase the ref counter for you.// This constructor will be used by the make_intrusive(), and also pybind11,// which wrap the intrusive_ptr holder around the raw pointer and incref// correspondingly (pybind11 requires raw pointer constructor to incref by// default).explicit intrusive_ptr(TTarget* target): intrusive_ptr(target, raw::DontIncreaseRefcount{}) {if (target_ != NullType::singleton()) {// We just created result.target_, so we know no other thread has// access to it, so we know we needn't care about memory ordering.// (On x86_64, a store with memory_order_relaxed generates a plain old// `mov`, whereas an atomic increment does a lock-prefixed `add`, which is// much more expensive: https://godbolt.org/z/eKPzj8.)TORCH_INTERNAL_ASSERT_DEBUG_ONLY(target_->refcount_ == 0 && target_->weakcount_ == 0,"intrusive_ptr: Newly-created target had non-zero refcounts. Does its ""constructor do something strange like incref or create an ""intrusive_ptr from `this`?");target_->refcount_.store(1, std::memory_order_relaxed);target_->weakcount_.store(1, std::memory_order_relaxed);}}

接著調用同一個檔案下不同簽名的constructor:

  // This constructor will not increase the ref counter for you.// We use the tagged dispatch mechanism to explicitly mark this constructor// to not increase the refcountexplicit intrusive_ptr(TTarget* target, raw::DontIncreaseRefcount) noexcept: target_(target) {}

做的事情實際上就只是更新TTarget*類型的成員變數target_。成員變數如下:

  TTarget* target_;

intrusive_ptr的類別宣告中有下面這麼一段注釋:

//  the following static assert would be nice to have but it requires
//  the target class T to be fully defined when intrusive_ptr<T> is instantiated
//  this is a problem for classes that contain pointers to themselves
//  static_assert(
//      std::is_base_of<intrusive_ptr_target, TTarget>::value,
//      "intrusive_ptr can only be used for classes that inherit from
//      intrusive_ptr_target.");

這裡說明TTarget必須繼承自intrusive_ptr_target

c10/core/StorageImpl.h中檢查一下StorageImpl是否符合這個條件:

struct C10_API StorageImpl : public c10::intrusive_ptr_target {//...
};

使用案例二

c10::Storage constructor

torch/include/c10/core/Storage.h

c10/core/Storage.h

struct C10_API Storage {// ...Storage(c10::intrusive_ptr<StorageImpl> ptr): storage_impl_(std::move(ptr)) {}// ...protected:c10::intrusive_ptr<StorageImpl> storage_impl_;
}

Storage類別有一個成員變數storage_impl_,是經intrusive_ptr包裝過後的StorageImpl。記得我們之前看過StorageImplc10::intrusive_ptr_target的子類別,這也印證了剛才所說intrusive_ptr必須搭配intrusive_ptr_target使用的規定。

注意到這裡初始化storage_impl_時用到了std::move,也就是調用了c10::intrusive_ptr的move constructor。

c10::intrusive_ptr move constructor

c10/util/intrusive_ptr.h

c10::intrusive_ptr的move constructor如下。把rhstarget_佔為己有後,將rhs.target_設為空:

  intrusive_ptr(intrusive_ptr&& rhs) noexcept : target_(rhs.target_) {rhs.target_ = NullType::singleton();}

下面這種move constructor支援不同類型的rhs,並多了相應的類型檢查功能,如果模板參數From可以被轉換成TTarget*(也就是target_的型別)才算成功:

  template <class From, class FromNullType>/* implicit */ intrusive_ptr(intrusive_ptr<From, FromNullType>&& rhs) noexcept: target_(detail::assign_ptr_<TTarget, NullType, FromNullType>(rhs.target_)) {static_assert(std::is_convertible<From*, TTarget*>::value,"Type mismatch. intrusive_ptr move constructor got pointer of wrong type.");rhs.target_ = FromNullType::singleton();}

至於為何要使用move constructor呢?

根據Why would I std::move an std::shared_ptr?:

std::shared_ptr reference count is atomic. increasing or decreasing the reference count requires atomic increment or decrement. This is hundred times slower than non-atomic increment/decrement, not to mention that if we increment and decrement the same counter we wind up with the exact number, wasting a ton of time and resources in the process.By moving the shared_ptr instead of copying it, we "steal" the atomic reference count and we nullify the other shared_ptr. "stealing" the reference count is not atomic, and it is hundred times faster than copying the shared_ptr (and causing atomic reference increment or decrement).

如果使用copy constructor的話,就需要atomic地增/減smart pointer的引用計數,而這個操作是十分耗時的,改用move constructor就可以免去這個atomic操作,節省大量時間。

demo

#include <torch/torch.h>
#include <iostream>
#include <vector>
#include <memory> //shared_ptr
#include <boost/intrusive_ptr.hpp>
#include <boost/detail/atomic_count.hpp> // boost::detail::atomic_count
#include <boost/checked_delete.hpp> // boost::checked_delete#define use_weak// #define use_shared
// #define use_boost
#define use_c10#ifdef use_c10
#define smart_ptr c10::intrusive_ptr
#define make_ptr c10::make_intrusive
#define weak_smart_ptr c10::weak_intrusive_ptr
#elif defined(use_boost)
#define smart_ptr boost::intrusive_ptr
#elif defined(use_shared)
#define smart_ptr std::shared_ptr
#define make_ptr std::make_shared
#define weak_smart_ptr std::weak_ptr
#endif#ifdef use_boost
template<class T>
class intrusive_ptr_base {
public:/*** 缺省构造函数*/intrusive_ptr_base(): ref_count(0) {// std::cout << "intrusive_ptr_base default constructor" << std::endl;}/*** 不允许拷贝构造,只能使用intrusive_ptr来构造另一个intrusive_ptr*/intrusive_ptr_base(intrusive_ptr_base<T> const&): ref_count(0) {std::cout << "intrusive_ptr_base copy constructor" << std::endl;}~intrusive_ptr_base(){std::cout << "intrusive_ptr_base destructor" << std::endl;}/*** 不允许进行赋值操作*/intrusive_ptr_base& operator=(intrusive_ptr_base const& rhs) {std::cout << "Assignment operator" << std::endl;return *this;}/*** 递增引用计数(放到基类中以便compiler能找到,否则需要放到boost名字空间中)*/friend void intrusive_ptr_add_ref(intrusive_ptr_base<T> const* s) {std::cout << "intrusive_ptr_base add ref" << std::endl;assert(s->ref_count >= 0);assert(s != 0);++s->ref_count;}/*** 递减引用计数*/friend void intrusive_ptr_release(intrusive_ptr_base<T> const* s) {std::cout << "intrusive_ptr_base release" << std::endl;assert(s->ref_count > 0);assert(s != 0);if (--s->ref_count == 0)boost::checked_delete(static_cast<T const*>(s));  //s的实际类型就是T,intrusive_ptr_base<T>为基类}/*** 类似于shared_from_this()函数*/boost::intrusive_ptr<T> self() {return boost::intrusive_ptr<T>((T*)this);}boost::intrusive_ptr<const T> self() const {return boost::intrusive_ptr<const T>((T const*)this);}int refcount() const {return ref_count;}private:///should be modifiable even from const intrusive_ptr objectsmutable boost::detail::atomic_count ref_count;};
#endif#ifdef use_c10
class MyVector : public c10::intrusive_ptr_target {
#elif defined(use_boost)
class MyVector : public intrusive_ptr_base<MyVector> {
#elif defined(use_shared)
class MyVector {
#endif
public:MyVector(const std::vector<int>& d) : data(d) {std::cout << "MyVector constructor" << std::endl;}~MyVector() {std::cout << "MyVector destructor" << std::endl;}std::vector<int> data;
};class A;
class B;#ifdef use_c10
class A : public c10::intrusive_ptr_target {
#elif defined(use_boost)
class A : public intrusive_ptr_base<A> {
#elif defined(use_shared)
class A {
#endif
public:A() {// std::cout << "A constructor" << std::endl;}~A() {std::cout << "A destructor" << std::endl;}#ifdef use_weakweak_smart_ptr<B> pointer;
#elsesmart_ptr<B> pointer;
#endif
};#ifdef use_c10
class B : public c10::intrusive_ptr_target {
#elif defined(use_boost)
class B : public intrusive_ptr_base<B> {
#elif defined(use_shared)
class B {
#endif
public:B() {// std::cout << "B constructor" << std::endl;}~B() {std::cout << "B destructor" << std::endl;}#ifdef use_weakweak_smart_ptr<A> pointer;
#elsesmart_ptr<A> pointer;
#endif
};int main() {{// 循環引用std::cout << "Circular reference" << std::endl;
#if defined(use_c10)smart_ptr<A> a_ptr = make_ptr<A>();smart_ptr<B> b_ptr = make_ptr<B>();
#elseA* a_raw_ptr = new A();B* b_raw_ptr = new B();std::cout << "Create A's smart pointer" << std::endl;smart_ptr<A> a_ptr(a_raw_ptr);std::cout << "Create B's smart pointer" << std::endl;smart_ptr<B> b_ptr(b_raw_ptr);
#endif#if !defined(use_boost)std::cout << "A ref count: " << a_ptr.use_count() << std::endl;std::cout << "B ref count: " << b_ptr.use_count() << std::endl;
#elsestd::cout << "A ref count: " << a_ptr->refcount() << std::endl;std::cout << "B ref count: " << b_ptr->refcount() << std::endl;
#endifstd::cout << "A's smart pointer references to B" << std::endl;a_ptr->pointer = b_ptr;std::cout << "B's smart pointer references to A" << std::endl;b_ptr->pointer = a_ptr;#if !defined(use_boost)std::cout << "A ref count: " << a_ptr.use_count() << std::endl;std::cout << "B ref count: " << b_ptr.use_count() << std::endl;
#elsestd::cout << "A ref count: " << a_ptr->refcount() << std::endl;std::cout << "B ref count: " << b_ptr->refcount() << std::endl;
#endif// shared_ptr, boost::intrusive_ptr: 引用計數都由1變成2,最後destructor不會被調用}std::cout << std::endl;{// 多指標指向同一物件std::cout << "Multiple smart pointer point to the same object" << std::endl;std::vector<int> vec({1,2,3});MyVector* raw_ptr = new MyVector(vec);smart_ptr<MyVector> ip, ip2;
#if defined(use_c10)std::cout << "Create 1st smart pointer" << std::endl;ip.reclaim(raw_ptr);// 無法用一個refcount非0的raw pointer創建c10::intrusive_ptr// std::cout << "Create 2nd smart pointer" << std::endl;// ip2.reclaim(raw_ptr);/*terminate called after throwing an instance of 'c10::Error'what():  owning_ptr == NullType::singleton() || owning_ptr->refcount_.load() == 0 || owning_ptr->weakcount_.load() INTERNAL ASSERT FAILED at "/root/Documents/installation/libtorch/include/c10/util/intrusive_ptr.h":471, please report a bug to PyTorch. TTarget violates the invariant that refcount > 0  =>  weakcount > 0Exception raised from reclaim at /root/Documents/installation/libtorch/include/c10/util/intrusive_ptr.h:471 (most recent call first):*/
#elsestd::cout << "Create 1st smart pointer" << std::endl;ip = smart_ptr<MyVector>(raw_ptr);std::cout << "Create 2nd smart pointer" << std::endl;ip2 = smart_ptr<MyVector>(raw_ptr);
#endif// shared_ptr: MyVector的destructor會被調用兩次,會出現Segmentation fault (core dumped)// boost::intrusive_ptr: 最後destructor會被調用}return 0;
}
rm -rf * && cmake -DCMAKE_PREFIX_PATH="<libtorch_installation_path>;<boost_installation_path>" .. && make && ./intrusive_ptr

循環引用

std::shared_ptr

Circular reference
Create A's smart pointer
Create B's smart pointer
A ref count: 1
B ref count: 1
A's smart pointer references to B
B's smart pointer references to A
A ref count: 2
B ref count: 2

改用std::weak_ptr

Circular reference
Create A's smart pointer
Create B's smart pointer
A ref count: 1
B ref count: 1
A's smart pointer references to B
B's smart pointer references to A
A ref count: 1
B ref count: 1
B destructor
A destructor

如果將AB的成員改成std::weak_ptr,在循環引用後它們的reference count不會增加,並且在離開scope後AB的destructor都會被調用。

boost::intrusive_ptr

Circular reference
Create A's smart pointer
intrusive_ptr_base add ref
Create B's smart pointer
intrusive_ptr_base add ref
A ref count: 1
B ref count: 1
A's smart pointer references to B
intrusive_ptr_base add ref
B's smart pointer references to A
intrusive_ptr_base add ref
A ref count: 2
B ref count: 2
intrusive_ptr_base release
intrusive_ptr_base release

c10::intrusive_ptr

Circular reference
A ref count: 1
B ref count: 1
A's smart pointer references to B
B's smart pointer references to A
A ref count: 2
B ref count: 2

如果改用c10::weak_intrusive_ptr,因為它沒有default constructor,會出現以下錯誤:

error: no matching function for call to 'c10::weak_intrusive_ptr<B>::wea
k_intrusive_ptr()'

多指標指向同一物件

std::shared_ptr

Multiple smart pointer point to the same object
MyVector constructor
Create 1st smart pointer
Create 2nd smart pointer
MyVector destructor
MyVector destructor
Segmentation fault (core dumped)

boost::intrusive_ptr

Multiple smart pointer point to the same object
MyVector constructor
Create 1st smart pointer
intrusive_ptr_base add ref
Create 2nd smart pointer
intrusive_ptr_base add ref
intrusive_ptr_base release
intrusive_ptr_base release
MyVector destructor
intrusive_ptr_base destructor

c10::intrusive_ptr

Multiple smart pointer point to the same object
MyVector constructor
Create 1st smart pointer

相关文章:

PyTorch中的intrusive_ptr

PyTorch中的intrusive_ptr 前言 intrusive_ptr與unique_ptr&#xff0c;shared_ptr等一樣&#xff0c;都是smart pointer。但是intrusive_ptr比較特別&#xff0c;它所指向的物件類型必須繼承自intrusive_ptr_target&#xff0c;而intrusive_ptr_target必須實現引用計數相關的…...

webrtc-stream编译报错记录

磁盘空间不足错误 错误信息 677.2 fatal: cannot create directory at blink/web_tests/external/wpt: No space left on device说明&#xff1a;这个错误是由于本地在配置docker资源时所给磁盘空间太小导致&#xff0c;直接根据镜像大小合理分配资源大小即可 pushd和popd执…...

什么是Docker CLI

Docker CLI&#xff08;命令行界面&#xff09;是一个工具&#xff0c;允许用户通过命令行或终端与Docker进行交互。Docker是一个开源平台&#xff0c;用于开发、运送和运行应用程序。Docker使用容器化技术来打包应用程序及其依赖项&#xff0c;以确保在不同环境中的一致性和隔…...

Java项目_家庭记账(简易版)

文章目录 简介代码实现 简介 该项目主要用来练习&#xff0c;Java的变量&#xff0c;运算符&#xff0c;分支结构和循环结构的知识点。 程序界面如下&#xff1a; 登记收入 登记支出 收支明细 程序退出 代码实现 package project;import java.util.Scanner;import sta…...

vscode json文件添加注释报错

在vscode中创建json文件&#xff0c;想要注释一波时&#xff0c;发现报了个错&#xff1a;Comments are not permitted in JSON. (521)&#xff0c;意思是JSON中不允许注释 以下为解决方法&#xff1a; 在vscode的右下角中找到这个&#xff0c;点击 在出现的弹窗中输入json wit…...

vue3移动端嵌入pdf的两种办法

1.使用embed嵌入 好处&#xff1a;简单&#xff0c;代码量少&#xff0c;功能齐全 缺点&#xff1a;有固定样式&#xff0c;难以修改&#xff0c;不可定制 <embed class"embedPdf" :src"pdfurl" type"application/pdf">2.使用vue-pdf-e…...

中文编程开发语言工具系统化教程初级1上线

中文编程系统化教程初级1 学习编程捷径&#xff1a;&#xff08;不论是正在学习编程的大学生&#xff0c;还是IT人士或者是编程爱好者&#xff0c;在学习编程的过程中用正确的学习方法 可以达到事半功倍的效果。对于初学者&#xff0c;可以通过下面的方法学习编程&#xff0c;…...

零售数据分析模板分享(通用型)

零售数据来源多&#xff0c;数据量大&#xff0c;导致数据的清洗整理工作量大&#xff0c;由于零售的特殊性&#xff0c;其指标计算组合更是多变&#xff0c;进一步导致了零售数据分析工作量激增&#xff0c;往往很难及时分析数据&#xff0c;发现问题。那怎么办&#xff1f;可…...

Spring Cloud之微服务

目录 微服务 微服务架构 微服务架构与单体架构 特点 框架 总结 SpringCloud 常用组件 与SpringBoot关系 版本 微服务 微服务&#xff1a;从字面上理解即&#xff1a;微小的服务&#xff1b; 微小&#xff1a;微服务体积小&#xff0c;复杂度低&#xff0c;一个微服…...

Linux命令(104)之date

linux命令之date 1.date介绍 linux命令date用来设置和显示系统日期和时间 2.date用法 date [参数] date参数 参数说明-s修改并设置时间-d可以显示以前和未来的时间%H小时%M分钟%S秒%X等价于%H %M %S%F显示当前所有时间属性%Y完整年份%m月%d日%A星期的全称 3.实例 3.1.当前…...

微信小程序投票管理系统:打造智能、便捷的投票体验

前言 随着社交网络的兴起和移动互联网的普及&#xff0c;人们对于参与和表达意见的需求越来越强烈。在这个背景下&#xff0c;微信小程序投票管理系统应运而生。它为用户提供了一个智能、便捷的投票平台&#xff0c;使用户可以轻松创建和参与各种类型的投票活动。本文将详细介…...

【算法训练-动态规划 五】【二维DP问题】编辑距离

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【动态规划】&#xff0c;使用【数组】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为&…...

Windows电脑如何录制电脑桌面?

如果你使用的电脑是Windows系统&#xff0c;那你是不是想知道如何在Windows电脑上录制电脑桌面&#xff1f; 本文以win10为例&#xff0c;好消息是&#xff0c;Windows 10电脑自带录屏工具&#xff0c;你可以直接使用此录屏工具轻松录制视频&#xff0c;而无需下载其他第三方软…...

ubuntu18.04双系统安装(2023最新最详细)以及解决重启后发现进不了Ubuntu问题

目录 一.简介 二.安装教程 1.首先确定了电脑的引导格式是UEFIGPT还是BIOSMBR 2. 使用Windows磁盘管理划分足够的磁盘空间 3. 开始安装 三.重启后发现自动进入WIN10系统了&#xff0c;进不了Ubuntu&#xff1f; 一.简介 Linux是一种自由和开放源代码的操作系统内核&#x…...

Springboot + screw 数据库快速开发文档

1、方式1 引入依赖 <dependency><groupId>cn.smallbun.screw</groupId><artifactId>screw-core</artifactId><version>1.0.5</version></dependency> /*** 文档生成 Springboot2.X screw数据库快速开发文档&#xff08;74&…...

2 第一个Go程序

概述 在上一节的内容中&#xff0c;我们介绍了Go的前世今生&#xff0c;包括&#xff1a;Go的诞生、发展历程、特性和应用领域。从本节开始&#xff0c;我们将正式学习Go语言。Go语言是一种编译型语言&#xff0c;也就是说&#xff0c;Go语言在运行之前需要先进行编译&#xff…...

Leetcode—2678.老人的数目【简单】

2023每日刷题&#xff08;八&#xff09; Leetcode—2678.老人的数目 int countSeniors(char ** details, int detailsSize){ int ans 0; int i; int tens 0; int ones 0; for(i 0; i < detailsSize; i) { tens ((details i) 11) - ‘0’; ones ((details i) 12)…...

解决 /bin/bash^M: bad interpreter: No such file or directory

问题描述 linux 系统中知行*.sh 文件报/bin/bash^M: bad interpreter: No such file or directory 原因&#xff1a; .sh文件是在windows系统编写的&#xff0c;在linux执行就有问题 解决过程 转化下格式执行如下命令 # dos2unix app.sh 结果bash: dos2unix: command not …...

Spring Cloud之服务注册与发现(Eureka)

目录 Eureka 介绍 角色 实现流程 单机构建 注册中心 服务提供者 服务消费者 集群搭建 注册中心 服务提供者 自我保护机制 原理分析 Eureka 介绍 Eureka是spring cloud中的一个负责服务注册与发现的组件&#xff0c;本身是基于REST的服务&#xff0c;同时还提供了…...

Rust-后端服务调试入坑记

这篇文章收录于Rust 实战专栏。这个专栏中的相关代码来自于我开发的笔记系统。它启动于是2023年的9月14日。相关技术栈目前包括&#xff1a;Rust&#xff0c;Javascript。关注我&#xff0c;我会通过这个项目的开发给大家带来相关实战技术的分享。 如果你关注过我的Rust 实战里…...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…...

visual studio 2022更改主题为深色

visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中&#xff0c;选择 环境 -> 常规 &#xff0c;将其中的颜色主题改成深色 点击确定&#xff0c;更改完成...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...

GO协程(Goroutine)问题总结

在使用Go语言来编写代码时&#xff0c;遇到的一些问题总结一下 [参考文档]&#xff1a;https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现&#xff1a; 今天在看到这个教程的时候&#xff0c;在自己的电…...

libfmt: 现代C++的格式化工具库介绍与酷炫功能

libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库&#xff0c;提供了高效、安全的文本格式化功能&#xff0c;是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全&#xff1a…...

算法打卡第18天

从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7…...

结构化文件管理实战:实现目录自动创建与归类

手动操作容易因疲劳或疏忽导致命名错误、路径混乱等问题&#xff0c;进而引发后续程序异常。使用工具进行标准化操作&#xff0c;能有效降低出错概率。 需要快速整理大量文件的技术用户而言&#xff0c;这款工具提供了一种轻便高效的解决方案。程序体积仅有 156KB&#xff0c;…...