当前位置: 首页 > 编程日记 > 正文

面向隐私AI的TensorFlow深度定制化实践

作者 | Rosetta团队

出品 | AI科技大本营(ID:rgznai100)

之前我们整体上介绍了基于深度学习框架开发隐私 AI 框架中的工程挑战和可行解决方案。在这一篇文章中,我们进一步结合 Rosetta 介绍如何定制化改造 TensorFlow 中前后端相关组件,以集成 MPC 等隐私计算技术,同时保留对 TensorFlow 接口 API 的复用,从而实现我们上一篇文章中所强调的“系统易用性”。

目前 Rosetta 主要基于 TensorFlow 1.14 CPU 版本加以开发(以下简称 TensorFlow 为 TF),这是因为 TF 1.x 目前在工业界中实际应用是较为广泛的,而引入动态图等高级功能的 TF 2.0,则由于接口的不向后兼容等问题,仍没有得到大规模的落地。后续我们也将在 Rosetta 本身功能稳定的基础上考虑支持 TF 2.0。下面就让我们开始吧。

TensorFlow 快速回顾

想要基于 AI 框架进一步扩展引入隐私计算功能,第一步就是需要比较深入的了解这些 AI 框架,所以首先让我们简单回顾一下TF中核心的一些概念以及其宏观的内部处理过程。

TensorFlow 的核心概念

  • Tensor(张量)

深度学习需要完成对大量高维度复杂数据的处理,在TensorFlow中,用Tensor来封装同一类型数据的高维数组。其中,基础类型除了各种不同精度的整数、浮点数外,还支持tf.string类型,这给我们提供了进行自定义类型改造的可能性。

一个三维Tensor (图片来自网络)

  • Operation(算子)

Operation(算子,有时也称“操作”)用来封装对于 Tensor 的处理逻辑。同时也是连接 TF 的前端和后端之间逻辑处理的基本单元,在实际使用中,用户可以使用keras等上层封装 API 更方便的表达复杂计算逻辑,但是这些上层模块的内部,也会最终调用各个算子来完成逻辑的表达。

  • Graph(计算图)

用户在 TF 前端调用各 API 形成的完整计算逻辑,在内部会以 dataflow graph 的形式来表达。在这一有向无环图(DAG)上,以算子等作为节点,以 Tesnor 等作为边来指明数据的流动路径。在 graph 上,有些节点是 TF 框架自身根据需要添加的,比如,用户在training算法阶段时,只需要调用各种优化器(Optimizer)的minimize方法,TF 自身就会自动的找到前向图中各算子所对应的梯度算子,并按照数学上的链式求导法则,构建出反向梯度子图。

TensorFlow 数据流计算图 (图片来自 TensorFlow 社区)

  • Session(会话)

Session 主要是在实际执行 graph 时对一次执行的上下文进行维护处理。当用户调用其run方法时,TF 就会分析为了获取这一次的计算目标所需要运行的子图,并结合 TF 内置的强大的并行优化、分布式执行等模块,将所需要执行的逻辑进一步拆分为各个子图,各自映射到当前的可用设备资源上,最终调度这些设备以并行的方式高效完成计算任务。

TensorFlow 分布式并行执行 (图片来自网络)

TensorFlow 的 codebase 本身还是很复杂的,篇幅所限,难以在此对 TensorFlow 进行深入的介绍,感兴趣的读者可以参考 TensorFlow 社区中其他优秀文章以进一步学习。

TensorFlow 自定义算子库的扩展方法

TF 提供了较为丰富的扩展方法,除了在 Python 层可以基于内置的丰富算子集合,通过模块的继承、组装等方式得到自定义的功能之外,还可以在后端 C++ 层自定义自己的算子 [2]。在后端基于 Custom C++ op 机制进行扩展相比于在前端层进行扩展有一些特别的优势:

  • 有时候基于现有 TF 原生算子表达上层自定义逻辑很困难,而在后端实现则更灵活自由;

  • 通过后端 Custom C++ op,可以以更加高效的方式实现自己的逻辑,可以在其中进行更底层的、面向编译器等的各种优化;

整体上看,基于 TF 的扩展工具,使用 custom C++ op,只需要完成以下四步即可:

1.通过 TF 提供的 C++ 宏工具注册新的 op。这主要是定义好这个 op 的输入输出类型、名称等接口信息。例如在Rosetta 中可以如下定义一个新的 op:

 REGISTER_OP("RttMatmul").Input("x: string").Input("y: string").Output("res: string").Attr("transpose_a: bool = false").Attr("transpose_b: bool = false").SetIsStateful();

2.在 C++ 中具体的实现这个 op 所对应的内部处理逻辑,这就是所谓的 后端 “kernel”。TF 提供了一些方便的基类接口,用户一般只需要定义一个子类,override 实现其中的compute方法即可,例如:

 template <typename Device>
class RttMatMulOp : public OpKernel {public:explicit RttMatMulOp(OpKernelConstruction* context) : OpKernel(context) {OP_REQUIRES_OK(context, context->GetAttr("transpose_a", &transpose_a_));OP_REQUIRES_OK(context, context->GetAttr("transpose_b", &transpose_b_));}void Compute(OpKernelContext* context) override {// Check if the dimensions of the two matrices are validconst Tensor& x = context->input(0);const Tensor& y = context->input(1);// detailed implementation...}
}

3.基于 REGISTER_KERNEL_BUILDER 这样的宏,将上面所定义的接口和内部的实现给绑定起来。这是因为 TF 支持基于不同的输入、输出类型和所运行的底层设备架构来定义同一个算子不同的内部实现,所以用户可以定义多种kernel实现,告知给系统什么场景下运行具体哪一个kernel,在实际运行时,TF 就可以根据不同的设备、数据流上下文调用不同的kernel来实际执行此 op。例如:

 REGISTER_KERNEL_BUILDER(Name("RttMatmul").Device(DEVICE_CPU), RttMatMulOp);

4.将你的后端算子库编译为一个动态库 so 文件后,在 Python 层调用接口引入此模块,然后就可以如同调用原生算子一样的方式来调用这些自定义算子了。例如:

 # load librtt_ops.so
_rtt_ops_lib = os.path.dirname(__file__) + '/../../../librtt-ops.so'
rtt_ops = tf.load_op_library(_rtt_ops_lib)
# now, you can use the ops in this library as rtt_ops.rtt_matmul

如果你需要在模型训练程序中调用这个自定义算子,你还需要在 Python 层通过@ops.RegisterGradient("XXXOp") 来注册这个算子对应的梯度算子,通过这种方式,TF 就可以在自动构建反向梯度图时自动的实现对自定义算子梯度的集成。

Rosetta 利用 TF 这一扩展机制引入两类算子:中间过渡层 RttOps 算子库和隐私计算 SecureOps 算子库,前者是为了支持面向自定义数据类型的计算图的构建,后者是为了对接后端隐私计算功能,并在执行图时进行动态绑定。之所以从设计上区分这两类算子,是因为可以进一步解耦图的构建和图的执行,提供更多的灵活性。引入了这两个基础的算子库之后,就可以进一步的进行整体的改造了。

RttOp 算子库

与后端 MPC 隐私计算完全无关的辅助中间层,一系列的“浮标”置位算子,支持自定义 Tensor 类型。其内部默认的实现逻辑是和对应的 TF 原生算子一样的。

SecureOp 算子库

完整的前后端算子库,注册了对应的梯度函数;在内部实现中调用隐私协议层的抽象算子接口实现和 TF 的对接。

Rosetta 对 TensorFlow 的深度定制化

如上一篇文章所整体介绍的那样,作为面向实际工业落地目标的隐私 AI 框架,Rosetta 对于 TF 的改造原则始终是为了提供更加便于 AI 开发者使用的上层接口,以及兼顾系统后端隐私协议的可扩展性。

Rosetta 整体工程架构

从系统架构和代码上看,改造的入口可以分为两大部分:

1,后端 C++ 部分的适配定制。主要以自定义算子的kernel形式进行适配。大部分接口的输入输出参数是以 tf.string 基础类型的Tensor,里面封装的是自定义的密文数据。在隐私算子 SecureOps 的 kernel内部会进一步调用统一的密码协议接口来完成 TF 到隐私计算功能的联通。

2,前端 Python 部分的适配定制。这里除了在Python前端引入我们自定义的算子库之外,还需要进一步改造 TF 中的自动求导功能等模块以实现对于新隐私算子的自动构建图、自动求导的支持。

从对于程序的动态处理角度来看,如前一篇文章所说,Rosetta 是经过两个阶段的 Pass,来完成到底层多方协作的 MPC 处理程序的转换。这里大部分的基于 TF 的前后端改造都是为了完成 Static Pass 阶段的转换,即将原生Tensor转换为支持自定义密文类型的RttTensor,将原生Operation 转换为支持tf.string格式输入输出的RttOp,并最终在图开始启动时进一步的转换为承载实际MPC操作的SecureOp 。

细心的读者可以看出,上面在介绍 TF 的 custom C++ op 扩展机制的同时,我们已经展示了如何定义 Rosetta 中的单个新算子。接下来,我们介绍如何基于这些算子实现计算图的分阶段转换。

计算图的转换构建过程

  • 引入 rosetta 库时

用户在前端执行 import lattciex.rosetta 之后,Rosetta 就会用 RttOp 静态替换掉原生 TF 中对应的原生 API 算子,且各个原生 Tensor 也会被包装一层到 RttTensor,其与原生 Tensor 的主要区别是,其数据的基础类型是tf.string,且对应的计算算子是RttOp。这种基础类型的转换是基于 RttOp 算子库中的TfToRtt 和RttToTf两个用于类型转换的算子来完成的。

  • 调用 Session.run 接口时

我们同样 hook 了 Session.run 入口,在其内部完成从上一步骤中 RttOp算子 到 SecureOp 算子的转换。如果用户使用 TensorBoard 工具查看此时的运行图,就会看到我们在图上添加了一个和原生 TF 计算图基本同构的新子图,这个子图就是由SecureOp构成。

TensorBoard可以查看得到的SecureOp计算图

和上文介绍的原生 TF 中的完整图构建过程一样,如果用户的程序含有模型训练过程,调用了优化器 Optimizer 的minimize方法,则我们还需要完成对 SecureOp 的反向梯度图自动生成的支持。

首先,我们需要注册各个SecureOp算子所对应的梯度函数。比如对于隐私矩阵乘法算子SecureMatMul,我们按照底层梯度的计算逻辑,定义其梯度函数如下:

@ops.RegisterGradient("SecureMatmul")
def SecureMatMulGrad(op, grad):"""The gradient for the Secure MatMul operator."""t_a = op.get_attr("transpose_a")t_b = op.get_attr("transpose_b")a = op.inputs[0]b = op.inputs[1]if not t_a and not t_b:grad_a = SecureMatMul(grad, b, transpose_b=True)grad_b = SecureMatMul(a, grad, transpose_a=True)elif not t_a and t_b:grad_a = SecureMatMul(grad, b)grad_b = SecureMatMul(grad, a, transpose_a=True)elif t_a and not t_b:grad_a = SecureMatMul(b, grad, transpose_b=True)grad_b = SecureMatMul(a, grad)elif t_a and t_b:grad_a = SecureMatMul(b, grad, transpose_a=True, transpose_b=True)grad_b = SecureMatMul(grad, a, transpose_a=True, transpose_b=True)return grad_a, grad_b

此外,由于我们使用 tf.string 来统一承载自定义的密文数据类型,而 TF 本身是不支持对于 tf.string 类型算子的自动求导的,所以 Rosetta 中还对tf.python.ops.gradients_util等入口进行了 hook 改造。比如,在下面这里,我们设定当 tensor 的基础类型为 string 时仍可以继续进行反向传播:

反向梯度图的自动生成

通过这些精细的定制化改造,最终就可以实现反向梯度子图的自动生成,可以极大的降低用户上手隐私计算的开发难度。

补充说明

  • 并非所有的算子都需要转换为SecureOp,这是因为如果一个局部子图中全部的输入都是本地的常量(公开的写定到代码中的数据,无需保护),那么就没有必要将这个子图转换为多方协作的隐私计算方式计算,这样可以减少不必要的计算时间。

  • 转换时,由于此时知道了即将运行的完整子图的信息,比如 DAG 图上有多少了算子需要运行,所以可以在这里进行一些定制化的优化,比如优化底层协议中多方之间的并发通讯。

在通过上述过程完成在前端层到SecureOp图的构建后,接下里就是依赖 TF 自身的图执行引擎来调度执行各个 SecureOp的后端 kernel 实现了,在这个kernel中,为了和具体使用的隐私计算技术解耦,我们所调用的是密码协议接口,比如SecureMatMul里最终通过如下代码片段来调用内部“隐私计算引擎”。这里的内部细节,我们会在后续内容中加以介绍。

// call protocol ops
vector<string> outstr(m*n);
ProtocolManager::Instance()->GetProtocol()->GetOps(msg_id().str())->Matmul(in1, in2, outstr, &attrs_);

小结

在本篇文章中,我们进一步介绍了 Rosetta 是如何深度的适配、定制化改造 TensorFlow 的各个组件以引入隐私计算功能的。与其他隐私 AI 开源框架相比,Rosetta 由于需要同时对 TensorFlow 的前端和后端进行扩展,并且完全的复用对上层的 API 接口,所以定制化的程度更加的深入。这里的改造是偏向于“系统易用性”这一目标的,不需要太多涉及 MPC 等隐私计算的技术,至于如何在后端引入”隐私计算引擎“,我们会在下一篇文章中介绍。

Rosetta 是首个国产自主研发基于主流TensorFlow的隐私AI框架,目前已经在Github开源(https://github.com/LatticeX-Foundation/Rosetta),欢迎关注我们,参与到Rosetta社区中来。

参考文献

[1] Abadi, Martín, et al. "Tensorflow: A system for large-scale machine learning." 12th {USENIX} symposium on operating systems design and implementation ({OSDI} 16). 2016.

[2] TensorFlow对定制化Op扩展的支持:https://www.tensorflow.org/guide/create_op

更多精彩推荐
  • 自拍卡通化,拯救动画师,StyleGAN再次玩出新花样

  • 秋天的第一杯奶茶该买哪家?Python 爬取美团网红奶茶店告诉你

  • AI视觉大牛朱松纯担任北大AI研究院院长,提出通过构建大任务平台走向通用AI

  • 2020互联网公司中秋礼盒大比拼!(文末送福利)

  • Azure Arc 正式商用、Power Platform+GitHub 世纪牵手,一文看懂 Ignite 2020

相关文章:

”拿来搞笑“的大学生活

”拿来搞笑“这词&#xff0c;是我舍友对我说过好几遍&#xff0c;我才觉得这词用来形容我大学的生活再恰当不过了&#xff0c;感谢他送给我这个词。 下面就列一下我大学期间”拿来搞笑“的事情&#xff1a; —&#xff1a;无偿捐血400毫升。当时认为是一件微不足道的事情&…

巧用CSS的Border属性

。作者&#xff1a;冯永曜 来源&#xff1a;黄山村夫 制作过网页的人都有为画线而烦恼的经历&#xff0c;本文介绍的小技巧也许对你有所帮助。我们先来认识一下“Border”&#xff08;画边框&#xff09;&#xff0c;它是CSS的一个属性&#xff0c;用它可以给能确定范围的HTML标…

阿里资深算法专家:如何突围大厂算法面试?

2020 届秋招&#xff0c;算法岗灰飞烟灭。最聪明的应届生 / 程序员 &#xff0c;都在极度竞争中&#xff0c;面临着前所未有的激烈 PK 。学生因“内卷”而迷茫&#xff1b;初级程序员遇职业发展瓶颈而困惑...面对重重压力&#xff0c;苦不堪言。to do list 上写满计划&#xff…

乡下人最嘲笑城里人的16件事,无语了!

一、出门「taxi」&#xff0c;乘电梯上七楼的健身房&#xff0c;然后在跑步机上挥汗如雨。 二、半夜上网&#xff0c;去歌厅、舞厅&#xff0c;困了不睡觉。之后失眠&#xff0c;再吃安眠药。 三、管儿子叫「小兔崽子」&#xff0c;管宠物狗叫儿子。 四、挑最有特色的饭店吃…

2017浅谈面试(一)

2017给自己一个目标&#xff0c;制定一份计划&#xff0c;新的开始&#xff0c;跟随创业团队风险无处不在&#xff0c;不过还是要选好Boss。 2016一个煎熬&#xff0c;悲剧&#xff0c;没发工资的日子&#xff0c;一等就等了5个月&#xff0c;零散的就拿到了一个半月工资&#…

乘风破浪的PTM:两年来预训练模型的技术进展

作者 | 张俊林来源 | 深度学习前沿笔记专栏Bert模型自18年10月推出&#xff0c;到目前为止快两年了。它卜一问世即引起轰动&#xff0c;之后&#xff0c;各种改进版本的预训练模型&#xff08;Pre-Training Model, PTM&#xff09;与应用如过江之鲫&#xff0c;层出不穷。Bert及…

DW中CSS属性详解

作者&#xff1a;未知 来源&#xff1a;5D多媒体 在Dreamweaver的CSS样式里包含了W3C规范定义的所有CSS1的属性&#xff0c;Dreamweaver把这些属性分为Type&#xff08;类型&#xff09;、Background&#xff08;背景&#xff09;、Block&#xff08;块&#xff09;、Box&a…

第十周项目5:贪心的富翁

上机内容&#xff1a;用循环语句完成累加 上机目的&#xff1a;学会循环语句的使用 /* * Copyright (c) 2012, 烟台大学计算机学院 * All rights reserved. * 作 者&#xff1a;孙锐 * 完成日期&#xff1a;2012 年 10 月 31 日 * 版 本 号&…

python requests返回的json对象用json.loads()时转为字典时编码变为了unicode

2019独角兽企业重金招聘Python工程师标准>>> 1.使用simplejson&#xff0c;loads的对象为str&#xff0c;否则还是会转码unicode import simplejson url "" payload {} headers {} r requests.post(url, datapayload, headersheaders)result simplej…

关于Dreamweaver乱码问题的解决方案

原作者&#xff1a;南宫彩虹出处&#xff1a;5D多媒体出现乱码&#xff0c;大致为两种情况&#xff1a; 一是没有标明主页制作所用的文字&#xff0c;这种情况下很简单就可以解决&#xff0c;在<HEAD>区加上<META http-equivContent-Type content"text/html; cha…

百度绝对控股,小度科技独立融资,投后估值200亿元

9月30日&#xff0c;百度宣布旗下智能生活事业群组业务&#xff08;以下简称“小度科技”&#xff09;完成了独立融资协议的签署&#xff0c;本轮融资由百度资本及CPE战略领投、IDG资本跟投&#xff0c;投后估值达约200亿元。作为百度的重要战略业务板块&#xff0c;百度公司对…

Android UI开发第二十五篇——分享一篇自定义的 Action Bar

Action Bar是android3.0以后才引入的&#xff0c;主要是替代3.0以前的menu和tittle bar。在3.0之前是不能使用Action Bar功能的。这里引入了自定义的Action Bar&#xff0c;自定义Action bar也不是完全实现了 Action bar功能&#xff0c;只是在外形上相似。自定义Action bar没有…

ADF12C 在线预览PDF文件 afinlineFrame

转载于:https://blog.51cto.com/feitai/1898433

批量消除虚线框

原作者&#xff1a;星之海洋出处&#xff1a;5D多媒体 各位想必都知道&#xff0c;οnfοcus”this.blur()”这条代码能消除链接时的虚线框&#xff0c;但你有没有想过&#xff0c;如果你的网页上有几个甚至上百个链接&#xff0c;而你又想要去掉上面那些讨厌的虚线框&#xf…

Python实战 | 送亲戚,送长辈,月饼可视化大屏来帮忙!

中秋节介绍中秋节&#xff0c;又称祭月节、月光诞、月夕、秋节、仲秋节、拜月节、月娘节、月亮节、团圆节等&#xff0c;是中国民间的传统节日。中秋节自古便有祭月、赏月、吃月饼、玩花灯、赏桂花、饮桂花酒等民俗&#xff0c;流传至今&#xff0c;经久不息。每年中秋节到&…

lamp一键安装包

lamp一键安装包 http://58.83.226.93/ http://www.centos.bz/lamp/ http://www.centos.bz/ lamp一键安装包 http://58.83.226.93/ http://www.centos.bz/lamp/ http://www.centos.bz/

快节奏的多人游戏同步 - 示例代码和在线演示

这是一个实现我《快节奏的多人游戏同步》系列文章中主要概念的客户端——服务器架构演示示例&#xff08;不包括实例插值&#xff0c;那块我还没弄完&#xff09;建议在阅读完这系列文章后再看这部分。代码是纯JavaScript写的&#xff0c;一页就装下了。少于400行代码&#xff…

探索IE浏览器窗口

探索IE浏览器窗口点燃灵感   星之海洋不知大家是否见过浏览器窗口&#xff08;哎呦&#xff0c;不要打我&#xff01;&#xff09;&#xff0c;其实&#xff0c;不要小瞧了这普普通通的windows&#xff0c;除了常用的window.open()与window.resizeTo()方法来开启窗口外&…

新转机!2020年想裸辞的程序员们注意了

近期&#xff0c;脉脉发布了《2020职场人裸辞现状调研报道》&#xff0c;报道显示2020最让职场人想裸辞的三大原因为&#xff1a;不开心、工资低、没有盼头。报告数据中还显示&#xff0c;工资不满预期是最让人想要裸辞的主要原因&#xff0c;但有超过6成职场人表示&#xff0c…

《ORACLE PL/SQL编程详细》,游标 ,函数,触发器。。

http://www.cnblogs.com/huyong/archive/2012/07/30/2614563.html

【Spark Summit East 2017】Spark化数据引擎

更多精彩内容参见云栖社区大数据频道https://yq.aliyun.com/big-data&#xff1b;此外&#xff0c;通过Maxcompute及其配套产品&#xff0c;低廉的大数据分析仅需几步&#xff0c;详情访问https://www.aliyun.com/product/odps。 本讲义出自Rohan Sharma在Spark Summit East 20…

将文本随意插入网页表单的 textarea

原作者&#xff1a;我佛山人出处&#xff1a;5D多媒体在网页表单的<textarea>中&#xff0c;我们有时候需要将文本插入到光标所在的地方&#xff0c;或是替换掉选区内的文字。这时候&#xff0c;用普通的.value"text"就无能为力了。下面这段测试代码可以解决这…

sharepoint 备份还原

sharepoint 2010备份和还原 sharepoint 2010中只有两种备份和还原的方法: 第一种: 使用命令行Stsadm来进行备份与还原 一. 备份操作 1, 首先创建一个批处理文件backup.bat,内容如下. "%systemdrive%\program Files\Common Files\Microsoft Shared\web server extension…

FCOS:全卷积一阶段Anchor Free物体检测器,多种视觉任务的统一框架

作者 | ronghuaiyang来源 | AI公园导读&#xff1a;这是众多Anchor Free中比较有影响力的一篇文章&#xff0c;不光是效果好&#xff0c;而且思路清楚&#xff0c;简单&#xff0c;不需要任何trick&#xff0c;并将众多的视觉任务统一到了一个框架中&#xff0c;值得一看。介绍…

Learn Jenkins the hard way (0) - Jenkins的罪与罚

写在开头 Jenkins是非常流行的开源的持续交付平台&#xff0c;拥有丰富的插件、文档与良好的社区。是国内大多数公司私有持续集成方案CI/CD服务器的选型。开发者可以快速的通过Jenkins搭架符合自己业务场景的pipeline&#xff0c;结合大量的开源插件&#xff0c;可以轻松的满足…

python中的文档字符串(docString)

python中的文档字符串(docString) - 泥土 - 博客园python中的文档字符串(docString)Posted on 2009-02-19 15:27 泥土 阅读(2122) 评论(0) 编辑 收藏 Python有一个很奇妙的特性&#xff0c;称为 文档字符串 &#xff0c;它通常被简称为 docstrings 。DocStrings是一个重要的工…

关于层的挡隔问题的探讨

原作者&#xff1a;我佛山人出处&#xff1a;5D多媒体1.被Flash挡住 设置Flash的参数&#xff1a;<param name"wmode" value"opaque"><object classid"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase"http://download.mac…

不是我放弃AI,是AI放弃了我!!

为什么程序员都觉得AI很难&#xff1f;Google 人工智能开发者专家彭靖田老师说——超90%的程序员在初学AI时&#xff0c;都会遇到下面3个问题&#xff1a;“应用方向太多了&#xff01;不知从何学起&#xff0c;也不知道学完做什么”“Python语法、机器学习/深度学习基础都能看…

Qt中两种定时器用法

在Qt中使用定时器有两种方法&#xff0c;一种是使用QObiect类的定时器&#xff1b;一种是使用QTimer类。定时器的精确性依赖于操作系统和硬件&#xff0c;大多数平台支持20ms的精确度。 1.QObject类的定时器 QObject是所有Qt对象的基类&#xff0c;它提供了一个基本的定时器。通…

有福了,J2EE面试题集锦(附答案)

一、基础问答 1.下面哪些类可以被继承? java.lang.Thread (T) java.lang.Number (T) java.lang.Double (F) java.lang.Math (F) java.lang.Void (F) java.lang.Class (F) java.lang.ClassLoader (T) 2.抽象类和接口的区别 (1)接口可以被多重…