相关内容:

tensorflow1.x——如何在python多线程中调用同一个session会话

=================================================

从前文tensorflow1.x——如何在python多线程中调用同一个session会话可以知道,使用python多线程调用同一个session中的计算图并不能有显著的性能提升,虽然有小幅度的提升但是该提升更像是一个python线程发送cuda计算指令的间隔期间另一个python线程发送cuda计算指令,从而填补了空闲,有了小幅度的提升,但是总体来看python多线程调用通过session并不能实现多个计算图的并行执行,当然这样可以用python线程的GIL来解释,因此本文就使用C++线程来调用通过session,以此来判断TensorFlow1.x中是否可以有效的实现多线程并发执行同一个session中的同个计算图的计算。

给出代码:TensorFlow1.x

一个线程的情况:

import tensorflow as tf
from tensorflow import keras
import numpy as np
import threading
import time def build():
n = 8
with tf.device("/gpu:0"):
x = tf.random_normal([n, 10])
x1 = tf.layers.dense(x, 10, activation=tf.nn.elu, name="fc1")
x2 = tf.layers.dense(x1, 10, activation=tf.nn.elu, name="fc2")
x3 = tf.layers.dense(x2, 10, activation=tf.nn.elu, name="fc3")
y = tf.layers.dense(x3, 10, activation=tf.nn.elu, name="fc4") queue = tf.FIFOQueue(10000, y.dtype, y.shape, shared_name='buffer')
enqueue_ops = []
for _ in range(1):
enqueue_ops.append(queue.enqueue(y))
tf.train.add_queue_runner(tf.train.QueueRunner(queue, enqueue_ops)) return queue # with sess.graph.as_default():
if __name__ == '__main__':
queue = build()
dequeued = queue.dequeue_many(4) with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
tf.train.start_queue_runners() a_time = time.time()
print(a_time)
for _ in range(100000):
sess.run(dequeued)
b_time = time.time()
print(b_time)
print(b_time-a_time) time.sleep(11111)

用时:

两个线程的情况:

import tensorflow as tf
from tensorflow import keras
import numpy as np
import threading
import time def build():
n = 8
with tf.device("/gpu:0"):
x = tf.random_normal([n, 10])
x1 = tf.layers.dense(x, 10, activation=tf.nn.elu, name="fc1")
x2 = tf.layers.dense(x1, 10, activation=tf.nn.elu, name="fc2")
x3 = tf.layers.dense(x2, 10, activation=tf.nn.elu, name="fc3")
y = tf.layers.dense(x3, 10, activation=tf.nn.elu, name="fc4") queue = tf.FIFOQueue(10000, y.dtype, y.shape, shared_name='buffer')
enqueue_ops = []
for _ in range(2):
enqueue_ops.append(queue.enqueue(y))
tf.train.add_queue_runner(tf.train.QueueRunner(queue, enqueue_ops)) return queue # with sess.graph.as_default():
if __name__ == '__main__':
queue = build()
dequeued = queue.dequeue_many(4) with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
tf.train.start_queue_runners() a_time = time.time()
print(a_time)
for _ in range(100000):
sess.run(dequeued)
b_time = time.time()
print(b_time)
print(b_time-a_time) time.sleep(11111)

用时:

四个线程的情况:

import tensorflow as tf
from tensorflow import keras
import numpy as np
import threading
import time def build():
n = 8
with tf.device("/gpu:0"):
x = tf.random_normal([n, 10])
x1 = tf.layers.dense(x, 10, activation=tf.nn.elu, name="fc1")
x2 = tf.layers.dense(x1, 10, activation=tf.nn.elu, name="fc2")
x3 = tf.layers.dense(x2, 10, activation=tf.nn.elu, name="fc3")
y = tf.layers.dense(x3, 10, activation=tf.nn.elu, name="fc4") queue = tf.FIFOQueue(10000, y.dtype, y.shape, shared_name='buffer')
enqueue_ops = []
for _ in range(4):
enqueue_ops.append(queue.enqueue(y))
tf.train.add_queue_runner(tf.train.QueueRunner(queue, enqueue_ops)) return queue # with sess.graph.as_default():
if __name__ == '__main__':
queue = build()
dequeued = queue.dequeue_many(4) with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
tf.train.start_queue_runners() a_time = time.time()
print(a_time)
for _ in range(100000):
sess.run(dequeued)
b_time = time.time()
print(b_time)
print(b_time-a_time) time.sleep(11111)

用时:

八个线程的情况:

import tensorflow as tf
from tensorflow import keras
import numpy as np
import threading
import time def build():
n = 8
with tf.device("/gpu:0"):
x = tf.random_normal([n, 10])
x1 = tf.layers.dense(x, 10, activation=tf.nn.elu, name="fc1")
x2 = tf.layers.dense(x1, 10, activation=tf.nn.elu, name="fc2")
x3 = tf.layers.dense(x2, 10, activation=tf.nn.elu, name="fc3")
y = tf.layers.dense(x3, 10, activation=tf.nn.elu, name="fc4") queue = tf.FIFOQueue(10000, y.dtype, y.shape, shared_name='buffer')
enqueue_ops = []
for _ in range(8):
enqueue_ops.append(queue.enqueue(y))
tf.train.add_queue_runner(tf.train.QueueRunner(queue, enqueue_ops)) return queue # with sess.graph.as_default():
if __name__ == '__main__':
queue = build()
dequeued = queue.dequeue_many(4) with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
tf.train.start_queue_runners() a_time = time.time()
print(a_time)
for _ in range(100000):
sess.run(dequeued)
b_time = time.time()
print(b_time)
print(b_time-a_time) time.sleep(11111)

用时:

================================================

可以看到使用C++多线程调用TensorFlow1.x中的同一个session下的同一个计算图也没有得到线性的加速,大致情况和python多线程的情况类似,确实开多线程调用同个session中的同个计算图性能会得到一定的提升,但是这个提升幅度很小,远不是和线程数成正比关系的,对于这种多线程与单线程相比较小幅度的提升更可能是在同个session的同个计算图中对cuda的调用都是使用一个命令队列的,之所以多线程会有一定性能提升是因为弥补上了cpu端对gpu端cuda发送命令的间隔上的空隙。

那么我们使用同一个session的两个计算分支,然后分别用两个线程来运行,那么效果如何呢?

给出代码:

import tensorflow as tf
from tensorflow import keras
import numpy as np
import threading
import time def build():
n = 8
with tf.device("/gpu:0"):
x = tf.random_normal([n, 10])
x1 = tf.layers.dense(x, 10, activation=tf.nn.elu, name="fc1")
x2 = tf.layers.dense(x1, 10, activation=tf.nn.elu, name="fc2")
x3 = tf.layers.dense(x2, 10, activation=tf.nn.elu, name="fc3")
y = tf.layers.dense(x3, 10, activation=tf.nn.elu, name="fc4") _x = tf.random_normal([n, 10])
_x1 = tf.layers.dense(_x, 10, activation=tf.nn.elu, name="fc1x")
_x2 = tf.layers.dense(_x1, 10, activation=tf.nn.elu, name="fc2x")
_x3 = tf.layers.dense(_x2, 10, activation=tf.nn.elu, name="fc3x")
_y = tf.layers.dense(_x3, 10, activation=tf.nn.elu, name="fc4x") queue = tf.FIFOQueue(10000, y.dtype, y.shape, shared_name='buffer')
enqueue_ops = []
for _ in range(1):
enqueue_ops.append(queue.enqueue(y))
enqueue_ops.append(queue.enqueue(_y))
tf.train.add_queue_runner(tf.train.QueueRunner(queue, enqueue_ops)) return queue # with sess.graph.as_default():
if __name__ == '__main__':
queue = build()
dequeued = queue.dequeue_many(4) with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
tf.train.start_queue_runners() a_time = time.time()
print(a_time)
for _ in range(100000):
sess.run(dequeued)
b_time = time.time()
print(b_time)
print(b_time-a_time) time.sleep(11111)

运算时间:

可以看到这个效果其实和同一个session下两个线程调用同个计算分支是相同的效果,那么这个问题会不会是出现在GPU上呢,如果我们的这两个计算分支分别在两个GPU上呢,给出代码:

import tensorflow as tf
from tensorflow import keras
import numpy as np
import threading
import time def build():
n = 8
with tf.device("/gpu:0"):
x = tf.random_normal([n, 10])
x1 = tf.layers.dense(x, 10, activation=tf.nn.elu, name="fc1")
x2 = tf.layers.dense(x1, 10, activation=tf.nn.elu, name="fc2")
x3 = tf.layers.dense(x2, 10, activation=tf.nn.elu, name="fc3")
y = tf.layers.dense(x3, 10, activation=tf.nn.elu, name="fc4") with tf.device("/gpu:1"):
_x = tf.random_normal([n, 10])
_x1 = tf.layers.dense(_x, 10, activation=tf.nn.elu, name="fc1x")
_x2 = tf.layers.dense(_x1, 10, activation=tf.nn.elu, name="fc2x")
_x3 = tf.layers.dense(_x2, 10, activation=tf.nn.elu, name="fc3x")
_y = tf.layers.dense(_x3, 10, activation=tf.nn.elu, name="fc4x") queue = tf.FIFOQueue(10000, y.dtype, y.shape, shared_name='buffer')
enqueue_ops = []
for _ in range(1):
enqueue_ops.append(queue.enqueue(y))
enqueue_ops.append(queue.enqueue(_y))
tf.train.add_queue_runner(tf.train.QueueRunner(queue, enqueue_ops)) return queue # with sess.graph.as_default():
if __name__ == '__main__':
queue = build()
dequeued = queue.dequeue_many(4) with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
tf.train.start_queue_runners() a_time = time.time()
print(a_time)
for _ in range(100000):
sess.run(dequeued)
b_time = time.time()
print(b_time)
print(b_time-a_time) time.sleep(11111)

0号显卡由于还在运行Firefox上的电影播放任务因此比一号卡使用率高了些,不过这个代码对两个显卡的利用率应该都是在32%左右。

运行时间:

可以看到对结果影响最多的还是使用两个线程分别调用两个显卡上两个不同的计算分支,从这里我们可以给出一个粗略的结论,那就是在TensorFlow中多线程调用session中的计算分支并不能有显著的性能提升,但是使用多线程调用同一个session中的不同GPU上的计算分支却可以极大的提升计算效率,不过这样的话和TensorFlow的多进程运行就比较像了,同时考虑到多线程编程的复杂性因此除了强化学习以外的机器学习代码如果想多线程加速运算那还不如使用单机多进程加速了。

其实,即使是深度学习框架中性能最强的TensorFlow在设计的最初也是针对单线程调用设计的,这里的单线程是只CPU端的单线程,如果CPU端是多线程调用同一个显卡上的计算图往往会由于cuda的stream默认队列的限制导致并不会有显著性能提升,当然从技术上来说完全深度学习框架完全可以在设计时就考虑到cuda指令执行的stream默认队列问题,或许是设计难度和适用面较窄的问题,即使是TensorFlow也没有提供多线程调用cuda kernel的多个stream队列,或许从目前来看多进程加速深度学习框架计算确实还是最优性价比的解决方法,虽然多进程的同步开销较大、用户编写代码的逻辑变得复杂,但是也完全可以弥补上深度学习框架提供该功能的厂家方的花销代价。

-----------------------------------

至少从目前来看,多线程调用深度学习框架其实还不如使用多进程调用深度学习框架来的合适,不过多进程调用深度学习框架必然要面对进程之间网络模型的同步问题,这又成了一个提高用户编码难度的一个点了。TensorFlow是属于少数提供多线程封装调用的深度学习框架,即使对于TensorFlow来说使用C++多线程调用不同cuda计算分支的性能也没有多进程调用不同cuda计算分支的性能高,再加上使用TensorFlow中的多线程本就是小众特征,难以切换到其他深度学习框架上使用,因此目前来看多进程调用cuda相比与多线程调用cuda才更是深度学习框架的正解,当然如果未来深度学习框架可以提高C++多线程调用不同计算分支的性能,那么或许以后有一天C++多线程调用深度框架的性能会优于多进程调用的。

---------------------------------------------

tensorflow1.x——如何在C++多线程中调用同一个session会话的更多相关文章

  1. 如何在C语言中调用Swift函数

    在Apple官方的<Using Swift with Cocoa and Objectgive-C>一书中详细地介绍了如何在Objective-C中使用Swift的类以及如何在Swift中 ...

  2. 【转载】如何在C语言中调用shell命令

    转载自:http://blog.csdn.net/chdhust/article/details/7951576 如何在C语言中调用shell命令 在linux操作系统中,很多shell命令使用起来非 ...

  3. 如何在Python脚本中调用外部命令(就像在linux shell或Windows命令提示符下输入一样)

    如何在Python脚本中调用外部命令(就像在linux shell或Windows命令提示符下输入一样) python标准库中的subprocess可以解决这个问题. from subprocess ...

  4. 解析如何在C语言中调用shell命令的实现方法【转】

    本文转自:http://www.jb51.net/article/37404.htm 1.system(执行shell 命令)相关函数 fork,execve,waitpid,popen表头文件 #i ...

  5. C# WPF 登录多线程中 “调用线程无法访问对象,因为另一个线程拥有该对象“

    造成这个错误的原因很多,以下是我遇到的 我的思路,开启一个线程A登录.因为服务器响应登录成功需要在主线程做一些操作,我这边需要用到主线程的窗口对象,我把窗口对象传到线程 A,直接用实例方法会有这个错误 ...

  6. 小程序:如何在wxml页面中调用JavaScript函数

    早上过来遇到一个这样的bug: 在计算百分比的时候没有保留小数点后2位,从而导致一些无法整除的结果显示太长 一开始,我以为这是一个很普通的bug,既然wxml在页面{{}}内支持简单的运算,我想也应该 ...

  7. 如何在多线程中调用winform窗体控件

    由于 Windows 窗体控件本质上不是线程安全的.因此如果有两个或多个线程适度操作某一控件的状态(set value),则可能会迫使该控件进入一种不一致的状态.还可能出现其他与线程相关的 bug,包 ...

  8. 如何在windows计划中调用备份sharepoint2010网站集的powershell脚本

    最近有个项目需要在在windows计划中使用powershell脚本备份sharepoint2010网站集,打开sharepoint的powershell执行命令管理界面的属性 查看: C:\Wind ...

  9. 如何在java程序中调用linux命令或者shell脚本

    转自:http://blog.sina.com.cn/s/blog_6433391301019bpn.html 在java程序中如何调用linux的命令?如何调用shell脚本呢? 这里不得不提到ja ...

  10. C++多线程中调用python api函数

    错误场景:一直等待全局锁. 解决方法: 一.首先定义一个封装类,主要是保证PyGILState_Ensure, PyGILState_Release配对使用,而且这个类是可以嵌套使用的. #inclu ...

随机推荐

  1. http与https详解

    1.http HTTP协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为广泛的一种网络传输协议,所有的WWW文件都必须遵守这个标准.HTTP是一个基于TC ...

  2. MySql 表数据的增、删、改、查

    数据表的增.删.改.查 前言 在学习 MySql 一定少不了对数据表的增.删.改.查,下面将详细讲解如何操作数据表. 前面已经建好了表 customer 列表如下: 插入数据 插入数据可以使用 INS ...

  3. linux elasticsearch-8.2.0安装

    1.下载,解压缩,命令行前不要留空格 官网下载地址: https://www.elastic.co/cn/downloads/elasticsearch https://artifacts.elast ...

  4. scab2

    package com.cmb.cox.utils;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;im ...

  5. python实现推送消息到微信公众号

    使用到库: Requests 实现方式: 微信已开放了对应的接口,直接通过python的requests库,发起请求,实现推送消息到公众号 微信公众号准备: 1.没有注册微信公众号,可以使用微信提供的 ...

  6. 认真学习CSS3-问题收集-101号-莫名其妙的row行高

    其他人都有事情,有些事情只好自己上阵,自己做,最踏实! 做了两个基本一样的页面,都是采用bootsrap+jquey+js的技术,业务内容就是简单的查询,加上一些简单的效果,没有啥特别的内容. 由于历 ...

  7. 基恩士PLC数据 转 Modbus RTU TCP项目案例

    1         案例说明 1.   设置网关采集基恩士PLC数据 2.   把采集的数据转成Modbus协议转发给其他系统. var code = "244226f8-1eed-48e4 ...

  8. 在WPF UWP WinUI中相同功能的用法的区别

    文件选择器 WPF:OpenFileDialog Microsoft.Win32.OpenFileDialog openFileDialog = new(); bool? result = openF ...

  9. ClickHouse介绍(四)ClickHouse使用操作

    ClickHouse使用操作 这章主要介绍在ClickHouse使用的各个操作的注意点.常规的统一语法不做详细介绍. 1. Join操作 在ClickHouse中,对连接操作定义了不同的精度,包含AL ...

  10. mermaid语法画图

    mermaid 脚本语言 graph TB 从上到下 graph BT 从下到上 graph RL 从右到左 graph LR 从左到右 graph LR; A001-->B001; graph ...