Tensorflow源码解析2 -- 前后端连接的桥梁 - Session

1 Session概述

Session是TensorFlow前后端连接的桥梁。用户利用session使得client能够与master的执行引擎建立连接,并通过session.run()来触发一次计算。它建立了一套上下文环境,封装了operation计算以及tensor求值的环境。

session创建时,系统会分配一些资源,比如graph引用、要连接的计算引擎的名称等。故计算完毕后,需要使用session.close()关闭session,避免引起内存泄漏,特别是graph无法释放的问题。可以显式调用session.close(),或利用with上下文管理器,或者直接使用InteractiveSession。

session之间采用共享graph的方式来提高运行效率。一个session只能运行一个graph实例,但一个graph可以运行在多个session中。一般情况下,创建session时如果不指定Graph实例,则会使用系统默认Graph。常见情况下,我们都是使用一个graph,即默认graph。当session创建时,不会重新创建graph实例,而是默认graph引用计数加1。当session close时,引用计数减1。只有引用计数为0时,graph才会被回收。这种graph共享的方式,大大减少了graph创建和回收的资源消耗,优化了TensorFlow运行效率。
 

2 默认session

op运算和tensor求值时,如果没有指定运行在哪个session中,则会运行在默认session中。通过session.as_default()可以将自己设置为默认session。但个人建议最好还是通过session.run(operator)和session.run(tensor)来进行op运算和tensor求值。

operation.run()

operation.run()等价于tf.get_default_session().run(operation)

@tf_export("Operation")
class Operation(object):# 通过operation.run()调用,进行operation计算def run(self, feed_dict=None, session=None):_run_using_default_session(self, feed_dict, self.graph, session)def _run_using_default_session(operation, feed_dict, graph, session=None):# 没有指定session,则获取默认sessionif session is None:session = get_default_session()# 最终还是通过session.run()进行运行的。tf中任何运算,都是通过session来run的。# 通过session来建立client和master的连接,并将graph发送给master,master再进行执行session.run(operation, feed_dict)

tensor.eval()

tensor.eval()等价于tf.get_default_session().run(tensor), 如下

@tf_export("Tensor")
class Tensor(_TensorLike):# 通过tensor.eval()调用,进行tensor运算def eval(self, feed_dict=None, session=None):return _eval_using_default_session(self, feed_dict, self.graph, session)def _eval_using_default_session(tensors, feed_dict, graph, session=None):# 如果没有指定session,则获取默认sessionif session is None:session = get_default_session()return session.run(tensors, feed_dict)

默认session的管理

tf通过运行时维护的session本地线程栈,来管理默认session。故不同的线程会有不同的默认session,默认session是线程作用域的。

# session栈
_default_session_stack = _DefaultStack()# 获取默认session的接口
@tf_export("get_default_session")
def get_default_session():return _default_session_stack.get_default()# _DefaultStack默认session栈是线程相关的
class _DefaultStack(threading.local):# 默认session栈的创建,其实就是一个listdef __init__(self):super(_DefaultStack, self).__init__()self._enforce_nesting = Trueself.stack = []  # 获取默认sessiondef get_default(self):return self.stack[-1] if len(self.stack) >= 1 else None

 

3 前端Session类型

session类图

会话Session的UML类图如下

分为两种类型,普通Session和交互式InteractiveSession。InteractiveSession和Session基本相同,区别在于

  1. InteractiveSession创建后,会将自己替换为默认session。使得之后operation.run()和tensor.eval()的执行通过这个默认session来进行。特别适合Python交互式环境。
  2. InteractiveSession自带with上下文管理器。它在创建时和关闭时会调用上下文管理器的enter和exit方法,从而进行资源的申请和释放,避免内存泄漏问题。这同样很适合Python交互式环境。

Session和InteractiveSession的代码逻辑不多,主要逻辑均在其父类BaseSession中。主要代码如下

@tf_export('Session')
class Session(BaseSession):def __init__(self, target='', graph=None, config=None):# session创建的主要逻辑都在其父类BaseSession中super(Session, self).__init__(target, graph, config=config)self._default_graph_context_manager = Noneself._default_session_context_manager = None
@tf_export('InteractiveSession')
class InteractiveSession(BaseSession):def __init__(self, target='', graph=None, config=None):self._explicitly_closed = False# 将自己设置为default sessionself._default_session = self.as_default()self._default_session.enforce_nesting = False# 自动调用上下文管理器的__enter__()方法self._default_session.__enter__()self._explicit_graph = graphdef close(self):super(InteractiveSession, self).close()## 省略无关代码## 自动调用上下文管理器的__exit__()方法,避免内存泄漏self._default_session.__exit__(None, None, None)self._default_session = None

BaseSession

BaseSession基本包含了所有的会话实现逻辑。包括会话的整个生命周期,也就是创建 执行 关闭和销毁四个阶段。生命周期后面详细分析。BaseSession包含的主要成员变量有graph引用,序列化的graph_def, 要连接的tf引擎target,session配置信息config等。
 

4 后端Session类型

在后端master中,根据前端client调用tf.Session(target='', graph=None, config=None)时指定的target,来创建不同的Session。target为要连接的tf后端执行引擎,默认为空字符串。Session创建采用了抽象工厂模式,如果为空字符串,则创建本地DirectSession,如果以grpc://开头,则创建分布式GrpcSession。类图如下

DirectSession只能利用本地设备,将任务创建到本地的CPU GPU上。而GrpcSession则可以利用远端分布式设备,将任务创建到其他机器的CPU GPU上,然后通过grpc协议进行通信。grpc协议是谷歌发明并开源的远程通信协议。
 

5 Session生命周期

Session作为前后端连接的桥梁,以及上下文运行环境,其生命周期尤其关键。大致分为4个阶段

  1. 创建:通过tf.Session()创建session实例,进行系统资源分配,特别是graph引用计数加1
  2. 运行:通过session.run()触发计算的执行,client会将整图graph传递给master,由master进行执行
  3. 关闭:通过session.close()来关闭,会进行系统资源的回收,特别是graph引用计数减1.
  4. 销毁:Python垃圾回收器进行GC时,调用session.__del__()进行回收。

生命周期方法入口基本都在前端Python的BaseSession中,它会通过swig自动生成的函数符号映射关系,调用C层的实现。

5.1 创建

先从BaseSession类的init方法看起,只保留了主要代码。

def __init__(self, target='', graph=None, config=None):# graph表示构建的图。TensorFlow的一个session会对应一个图。这个图包含了所有涉及到的算子# graph如果没有设置(通常都不会设置),则使用默认graphif graph is None:self._graph = ops.get_default_graph()else:self._graph = graphself._opened = Falseself._closed = Falseself._current_version = 0self._extend_lock = threading.Lock()# target为要连接的tf执行引擎if target is not None:self._target = compat.as_bytes(target)else:self._target = Noneself._delete_lock = threading.Lock()self._dead_handles = []# config为session的配置信息if config is not None:self._config = configself._add_shapes = config.graph_options.infer_shapeselse:self._config = Noneself._add_shapes = Falseself._created_with_new_api = ops._USE_C_API# 调用C层来创建sessionself._session = Noneopts = tf_session.TF_NewSessionOptions(target=self._target, config=config)self._session = tf_session.TF_NewSession(self._graph._c_graph, opts, status)

BaseSession先进行成员变量的赋值,然后调用TF_NewSession来创建session。TF_NewSession()方法由swig自动生成,在bazel-bin/tensorflow/python/pywrap_tensorflow_internal.py中

def TF_NewSession(graph, opts, status):return _pywrap_tensorflow_internal.TF_NewSession(graph, opts, status)

_pywrap_tensorflow_internal包含了C层函数的符号表。在swig模块import时,会加载pywrap_tensorflow_internal.so动态链接库,从而得到符号表。在pywrap_tensorflow_internal.cc中,注册了供Python调用的函数的符号表,从而实现Python到C的函数映射和调用。

// c++函数调用的符号表,Python通过它可以调用到C层代码。符号表和动态链接库由swig自动生成
static PyMethodDef SwigMethods[] = {// .. 省略其他函数定义// TF_NewSession的符号表,通过这个映射,Python中就可以调用到C层代码了。{ (char *)"TF_NewSession", _wrap_TF_NewSession, METH_VARARGS, NULL},// ... 省略其他函数定义
}

最终调用到c_api.c中的TF_NewSession()

// TF_NewSession创建session的新实现,在C层后端代码中
TF_Session* TF_NewSession(TF_Graph* graph, const TF_SessionOptions* opt,TF_Status* status) {Session* session;// 创建sessionstatus->status = NewSession(opt->options, &session);if (status->status.ok()) {TF_Session* new_session = new TF_Session(session, graph);if (graph != nullptr) {// 采用了引用计数方式,多个session共享一个图实例,效率更高。// session创建时,引用计数加1。session close时引用计数减1。引用计数为0时,graph才会被回收。mutex_lock l(graph->mu);graph->sessions[new_session] = Status::OK();}return new_session;} else {DCHECK_EQ(nullptr, session);return nullptr;}
}

session创建时,并创建graph,而是采用共享方式,只是引用计数加1了。这种方式减少了session创建和关闭时的资源消耗,提高了运行效率。NewSession()根据前端传递的target,使用sessionFactory创建对应的TensorFlow::Session实例。

Status NewSession(const SessionOptions& options, Session** out_session) {SessionFactory* factory;const Status s = SessionFactory::GetFactory(options, &factory);// 通过sessionFactory创建多态的Session。本地session为DirectSession,分布式为GRPCSession*out_session = factory->NewSession(options);if (!*out_session) {return errors::Internal("Failed to create session.");}return Status::OK();
}

创建session采用了抽象工厂模式。根据client传递的target,来创建不同的session。如果target为空字符串,则创建本地DirectSession。如果以grpc://开头,则创建分布式GrpcSession。TensorFlow包含本地运行时和分布式运行时两种运行模式。

下面来看DirectSessionFactory的NewSession()方法

class DirectSessionFactory : public SessionFactory {public:Session* NewSession(const SessionOptions& options) override {std::vector<Device*> devices;// job在本地执行const Status s = DeviceFactory::AddDevices(options, "/job:localhost/replica:0/task:0", &devices);if (!s.ok()) {LOG(ERROR) << s;return nullptr;}DirectSession* session =new DirectSession(options, new DeviceMgr(devices), this);{mutex_lock l(sessions_lock_);sessions_.push_back(session);}return session;}

GrpcSessionFactory的NewSession()方法就不详细分析了,它会将job任务创建在分布式设备上,各job通过grpc协议通信。

5.2 运行

通过session.run()可以启动graph的执行。入口在BaseSession的run()方法中, 同样只列出关键代码

class BaseSession(SessionInterface):def run(self, fetches, feed_dict=None, options=None, run_metadata=None):# fetches可以为单个变量,或者数组,或者元组。它是图的一部分,可以是操作operation,也可以是数据tensor,或者他们的名字String# feed_dict为对应placeholder的实际训练数据,它的类型为字典result = self._run(None, fetches, feed_dict, options_ptr,run_metadata_ptr)return resultdef _run(self, handle, fetches, feed_dict, options, run_metadata):# 创建fetch处理器fetch_handlerfetch_handler = _FetchHandler(self._graph, fetches, feed_dict_tensor, feed_handles=feed_handles)# 经过不同类型的fetch_handler处理,得到最终的fetches和targets# targets为要执行的operation,fetches为要执行的tensor_ = self._update_with_movers(feed_dict_tensor, feed_map)final_fetches = fetch_handler.fetches()final_targets = fetch_handler.targets()# 开始运行if final_fetches or final_targets or (handle and feed_dict_tensor):results = self._do_run(handle, final_targets, final_fetches,feed_dict_tensor, options, run_metadata)else:results = []# 输出结果到results中return fetch_handler.build_results(self, results)def _do_run(self, handle, target_list, fetch_list, feed_dict, options, run_metadata):# 将要运行的operation添加到graph中self._extend_graph()# 执行一次运行run,会调用底层C来实现return tf_session.TF_SessionPRunSetup_wrapper(session, feed_list, fetch_list, target_list, status)# 将要运行的operation添加到graph中def _extend_graph(self):with self._extend_lock:if self._graph.version > self._current_version:# 生成graph_def对象,它是graph的序列化表示graph_def, self._current_version = self._graph._as_graph_def(from_version=self._current_version, add_shapes=self._add_shapes)# 通过TF_ExtendGraph将序列化后的graph,也就是graph_def传递给后端with errors.raise_exception_on_not_ok_status() as status:tf_session.TF_ExtendGraph(self._session,graph_def.SerializeToString(), status)self._opened = True

逻辑还是十分复杂的,主要有一下几步

  1. 入参处理,创建fetch处理器fetch_handler,得到最终要执行的operation和tensor
  2. 对graph进行序列化,生成graph_def对象
  3. 将序列化后的grap_def对象传递给后端master。
  4. 通过后端master来run。

我们分别来看extend和run。

5.2.1 extend添加节点到graph中

TF_ExtendGraph()会调用到c_api中,这个逻辑同样通过swig工具自动生成。下面看c_api.cc中的TF_ExtendGraph()方法

// 增加节点到graph中,proto为序列化后的graph
void TF_ExtendGraph(TF_DeprecatedSession* s, const void* proto,size_t proto_len, TF_Status* status) {GraphDef g;// 先将proto反序列化,得到client传递的graph,放入g中if (!tensorflow::ParseProtoUnlimited(&g, proto, proto_len)) {status->status = InvalidArgument("Invalid GraphDef");return;}// 再调用session的extend方法。根据创建的不同session类型,多态调用不同方法。status->status = s->session->Extend(g);
}

后端系统根据生成的Session类型,多态的调用Extend方法。如果是本地session,则调用DirectSession的Extend()方法。如果是分布式session,则调用GrpcSession的相关方法。下面来看GrpcSession的Extend方法。

Status GrpcSession::Extend(const GraphDef& graph) {CallOptions call_options;call_options.SetTimeout(options_.config.operation_timeout_in_ms());return ExtendImpl(&call_options, graph);
}Status GrpcSession::ExtendImpl(CallOptions* call_options,const GraphDef& graph) {bool handle_is_empty;{mutex_lock l(mu_);handle_is_empty = handle_.empty();}if (handle_is_empty) {// 如果graph句柄为空,则表明graph还没有创建好,此时extend就等同于createreturn Create(graph);}mutex_lock l(mu_);ExtendSessionRequest req;req.set_session_handle(handle_);*req.mutable_graph_def() = graph;req.set_current_graph_version(current_graph_version_);ExtendSessionResponse resp;// 调用底层实现,来添加节点到graph中Status s = master_->ExtendSession(call_options, &req, &resp);if (s.ok()) {current_graph_version_ = resp.new_graph_version();}return s;
}

Extend()方法中要注意的一点是,如果是首次执行Extend(), 则要先调用Create()方法进行graph的注册。否则才是执行添加节点到graph中。

5.2.2 run执行图的计算

同样,Python通过swig自动生成的代码,来实现对C API的调用。C层实现在c_api.cc的TF_Run()中。

// session.run()的C层实现
void TF_Run(TF_DeprecatedSession* s, const TF_Buffer* run_options,// Input tensors,输入的数据tensorconst char** c_input_names, TF_Tensor** c_inputs, int ninputs,// Output tensors,运行计算后输出的数据tensorconst char** c_output_names, TF_Tensor** c_outputs, int noutputs,// Target nodes,要运行的节点const char** c_target_oper_names, int ntargets,TF_Buffer* run_metadata, TF_Status* status) {// 省略一段代码TF_Run_Helper(s->session, nullptr, run_options, input_pairs, output_names,c_outputs, target_oper_names, run_metadata, status);
}// 真正的实现了session.run()
static void TF_Run_Helper() {RunMetadata run_metadata_proto;// 调用不同的session实现类的run方法,来执行result = session->Run(run_options_proto, input_pairs, output_tensor_names,target_oper_names, &outputs, &run_metadata_proto);// 省略代码
}

最终会调用创建的session来执行run方法。DirectSession和GrpcSession的Run()方法会有所不同。后面很复杂,就不接着分析了。

5.3 关闭session

通过session.close()来关闭session,释放相关资源,防止内存泄漏。

class BaseSession(SessionInterface):def close(self):tf_session.TF_CloseSession(self._session, status)

会调用到C API的TF_CloseSession()方法。

void TF_CloseSession(TF_Session* s, TF_Status* status) {status->status = s->session->Close();
}

最终根据创建的session,多态的调用其Close()方法。同样分为DirectSession和GrpcSession两种。

::tensorflow::Status DirectSession::Close() {cancellation_manager_->StartCancel();{mutex_lock l(closed_lock_);if (closed_) return ::tensorflow::Status::OK();closed_ = true;}// 注销sessionif (factory_ != nullptr) factory_->Deregister(this);return ::tensorflow::Status::OK();
}

DirectSessionFactory中的Deregister()方法如下

void Deregister(const DirectSession* session) {mutex_lock l(sessions_lock_);// 释放相关资源sessions_.erase(std::remove(sessions_.begin(), sessions_.end(), session),sessions_.end());}

5.4 销毁session

session的销毁是由Python的GC自动执行的。python通过引用计数方法来判断是否回收对象。当对象的引用计数为0,且虚拟机触发了GC时,会调用对象的__del__()方法来销毁对象。引用计数法有个很致命的问题,就是无法解决循环引用问题,故会存在内存泄漏。Java虚拟机采用了调用链分析的方式来决定哪些对象会被回收。

class BaseSession(SessionInterface):  def __del__(self):# 先close,防止用户没有调用close()try:self.close()# 再调用c api的TF_DeleteSession来销毁sessionif self._session is not None:try:status = c_api_util.ScopedTFStatus()if self._created_with_new_api:tf_session.TF_DeleteSession(self._session, status)

c_api.cc中的相关逻辑如下

void TF_DeleteSession(TF_Session* s, TF_Status* status) {status->status = Status::OK();TF_Graph* const graph = s->graph;if (graph != nullptr) {graph->mu.lock();graph->sessions.erase(s);// 如果graph的引用计数为0,也就是graph没有被任何session持有,则考虑销毁graph对象const bool del = graph->delete_requested && graph->sessions.empty();graph->mu.unlock();// 销毁graph对象if (del) delete graph;}// 销毁session和TF_Session delete s->session;delete s;
}

TF_DeleteSession()会判断graph的引用计数是否为0,如果为0,则会销毁graph。然后销毁session和TF_Session对象。通过Session实现类的析构函数,来销毁session,释放线程池Executor,资源管理器ResourceManager等资源。

DirectSession::~DirectSession() {for (auto& it : partial_runs_) {it.second.reset(nullptr);}// 释放线程池Executorfor (auto& it : executors_) {it.second.reset();}for (auto d : device_mgr_->ListDevices()) {d->op_segment()->RemoveHold(session_handle_);}// 释放ResourceManagerfor (auto d : device_mgr_->ListDevices()) {d->ClearResourceMgr();}// 释放CancellationManager实例functions_.clear();delete cancellation_manager_;// 释放ThreadPool for (const auto& p_and_owned : thread_pools_) {if (p_and_owned.second) delete p_and_owned.first;}execution_state_.reset(nullptr);flib_def_.reset(nullptr);
}

 

6 总结

Session是TensorFlow的client和master连接的桥梁,client任何运算也是通过session来run。它是client端最重要的对象。在Python层和C++层,均有不同的session实现。session生命周期会经历四个阶段,create run close和del。四个阶段均由Python前端开始,最终调用到C层后端实现。由此也可以看到,TensorFlow框架的前后端分离和模块化设计是多么的精巧。


原文链接
本文为云栖社区原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/519753.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为减少用户电话排队,阿里研发了智能客服调度系统

提到调度&#xff0c;大家脑海中可能想起的是调度阿里云的海量机器资源&#xff0c;而对于阿里集团客户体验事业群&#xff08;CCO&#xff09;而言&#xff0c;我们要调度的不是机器&#xff0c;而是客服资源。今天&#xff0c;我们邀请阿里高级技术专家力君&#xff0c;为大家…

基于快速GeoHash,如何实现海量商品与商圈的高效匹配?

小叽导读&#xff1a;闲鱼是一款闲置物品的交易平台APP。通过这个平台&#xff0c;全国各地“无处安放”的物品能够轻松实现流动。这种分享经济业务形态被越来越多的人所接受&#xff0c;也进一步实现了低碳生活的目标。 今天&#xff0c;闲鱼团队就商品与商圈的匹配算法为我们…

独家揭秘!阿里大规模数据中心的性能分析

阿里妹导读&#xff1a;数据中心已成为支撑大规模互联网服务的标准基础设施。随着数据中心的规模越来越大&#xff0c;数据中心里每一次软件&#xff08;如 JVM&#xff09;或硬件&#xff08;如 CPU&#xff09;的升级改造都会带来高昂的成本。合理的性能分析有助于数据中心的…

云+X案例展 | 金融类:七牛云Pandora 助阵某银行实现日志智能管理

本案例由七牛云投递并参与评选&#xff0c;CSDN云计算独家全网首发&#xff1b;更多关于【云X 案例征集】的相关信息&#xff0c;点击了解详情丨挖掘展现更多优秀案例&#xff0c;为不同行业领域带来启迪&#xff0c;进而推动整个“云行业”的健康发展。银行作为国民经济的重要…

函数运行环境系统动态链接库版本太低?函数计算 fun 神助力分忧解难

背景 最近在处理线上工单的时候&#xff0c;遇到一个用户使用 nodejs runtime 时因为函数计算运行环境的 gcc 版本过低导致无法运行的问题&#xff0c;觉得非常有意思&#xff0c;所以深入的帮用户寻找了解决方案。觉得这个场景应该具有一定的通用性&#xff0c;所以在这篇文章…

蚂蚁金服核心技术:百亿特征实时推荐算法揭秘

文章提出一整套创新算法与架构&#xff0c;通过对TensorFlow底层的弹性改造&#xff0c;解决了在线学习的弹性特征伸缩和稳定性问题&#xff0c;并以GroupLasso和特征在线频次过滤等自研算法优化了模型稀疏性。在支付宝核心推荐业务获得了uvctr的显著提升&#xff0c;并较大地提…

老码农肺腑直言:为什么我不建议你学Python?

关于Python&#xff0c;我们听到最多的一句话就是&#xff1a;代码简洁。目前来看&#xff0c;代码量上C:Java:Python100:10:1。Python简洁度完胜&#xff0c;但这却也是他的“死亡缺点”&#xff01;今天想跟大家分享&#xff0c;学Python的一系列惊天大坑……Python钱多“话少…

如何评估深度学习模型效果?阿里工程师这么做

复杂的深度模型中&#xff0c;如果效果不好&#xff0c;是因为网络设计的欠缺&#xff1f;还是数据天然缺陷&#xff1f;是训练代码的bug&#xff1f;还是Tensorflow自身的问题&#xff1f;基于此&#xff0c;阿里工程师推出了DeepInsight深度学习质量平台&#xff0c;致力于解…

开发者如何赶上 5G 风口?

戳蓝字“CSDN云计算”关注我们哦&#xff01;随着5G正式步入商用&#xff0c;5G 技术引发广泛关注。据信息通信研究院《5G经济社会影响白皮书》预测&#xff0c;2030年&#xff0c;5G将直接带动的总产出、经济增加值、就业机会分别为6.3万亿元、2.9万亿元和800万个。据BOSS直聘…

罗辑思维在全链路压测方面的实践和工作笔记

业务的知名度越高&#xff0c;其背后技术团队承受的压力就越大。一旦出现技术问题&#xff0c;就有可能被放大&#xff0c;尤其是当服务的是对知识获取体验要求颇高的用户群体。 提供知识服务的罗辑思维主张“省时间的获取知识”&#xff0c;那么其技术团队在技术实践方面是如…

能用机器完成的,千万别堆工作量|持续集成中的性能自动化测试

1.背景 当前闲鱼在精益开发模式下&#xff0c;整个技术团队面临了诸多的能力落地和挑战&#xff0c;尤其是效能方面的2-1-1的目标(2周需求交付周期&#xff0c;1周需求开发周期&#xff0c;1小时达到发布标准)&#xff0c;具体可见 闲鱼工程师是如何构建持续集成流水线&#x…

详解GPU技术关键参数和应用场景

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | Hardy责编 | 阿秃随着云计算&#xff0c;大数据和人工智能技术发展&#xff0c;边缘计算发挥着越来越重要的作用&#xff0c;补充数据中心算力需求。计算架构要求多样化&#xff0c;需要不同的CPU架构来满足不断增长的算力需…

5款神器级别Github 的Chrome插件

文章目录1. Chrome插件一&#xff1a;octotree2. Chrome插件二&#xff1a;sourcegraph3. Chrome插件三&#xff1a;Enhanced GitHub4. Chrome插件四&#xff1a;octolinker5. Chrome插件五&#xff1a;gitzip for github1. Chrome插件一&#xff1a;octotree Octotree是一个 …

用AI说再见!“辣眼睛”的买家秀

阿里妹导读&#xff1a;提起买家秀和卖家秀&#xff0c;相信大家脑中会立刻浮现出诸多画面。同一件衣服在不同人、光线、角度下&#xff0c;会呈现完全不同的状态。运营小二需从大量的买家秀中挑选出高质量的图片。如果单纯靠人工来完成&#xff0c;工作量过于巨大。下面&#…

云+X案例展 | 电商零售类:WakeData助力叁拾加数字化变革

本案例由WakeData投递并参与评选&#xff0c;CSDN云计算独家全网首发&#xff1b;更多关于【云X 案例征集】的相关信息&#xff0c;点击了解详情丨挖掘展现更多优秀案例&#xff0c;为不同行业领域带来启迪&#xff0c;进而推动整个“云行业”的健康发展。在新零售时代下&#…

linux环境安装Kafka最新版本 jdk1.8

文章目录一、环境分布二、实战1. kafka下载2. 解压3. 配置4. 编写启动脚本5. 编写关闭脚本6. 赋予脚本可执行权限7. 脚本使用案例一、环境分布 软件版本jdk1.8kafkakafka_2.13-2.5.0 二、实战 kafka官网地址&#xff1a; http://kafka.apache.org/downloads 1. kafka下载 …

基于泛型编程的序列化实现方法

写在前面 序列化是一个转储-恢复的操作过程&#xff0c;即支持将一个对象转储到临时缓冲或者永久文件中和恢复临时缓冲或者永久文件中的内容到一个对象中等操作&#xff0c;其目的是可以在不同的应用程序之间共享和传输数据&#xff0c;以达到跨应用程序、跨语言和跨平台的解耦…

微服务架构下,解决数据一致性问题的实践

随着业务的快速发展&#xff0c;应用单体架构暴露出代码可维护性差、容错率低、测试难度大和敏捷交付能力差等诸多问题&#xff0c;微服务应运而生。微服务的诞生一方面解决了上述问题&#xff0c;但是另一方面却引入新的问题&#xff0c;其中主要问题之一就是&#xff1a;如何…

2019阿里云开年Hi购季满返活动火热报名中!

2019阿里云云上采购季活动已经于2月25日正式开启&#xff0c;从已开放的活动页面来看&#xff0c;活动分为三个阶段&#xff1a; 2月25日-3月04日的活动报名阶段、3月04日-3月16日的新购满返5折抢购阶段、3月16日-3月31日的续费抽豪礼5折抢购阶段。 整个大促活动包含1个主会场…