zoukankan      html  css  js  c++  java
  • 再探C#类与结构体究竟谁快——考虑栈变量、栈分配、64位整数、密封类

    上次我对C#类与结构体做了一次速度评测(http://blog.csdn.net/zyl910/article/details/6788417)。经过一段时间思索,发现还可以进一步探讨——

    第一、栈变量。上次的“硬编码”,是访问类中的静态变量的。若改为访问函数中的栈变量,性能会不会有所提高?
    第二、栈分配(stackalloc)。既然要测试栈变量,我们还可以顺便测试一下在栈上分配的内存块的访问性能。
    第三、64位整数。由于32位系统的成功,我们已经习惯了使用32位整数(int)。现在64位系统逐渐普及,我们得为此做好准备。对于指针操作时经常要用到的偏移量增减运算来说,是使用32位整数,还是使用64位整数,或写两套代码?这需要测试后才能决定。
    第四、密封类(sealed)。听说密封类能提高性能,我们可以测试一下。有两种测试方式,一是为原来的派生类增加sealed关键字,二是专门另外写一个密封类。我决定同时使用这两种方法,分别测试其性能。


    一、测试代码


      测试代码如下——

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Diagnostics;
    
    namespace TryPointerCall
    {
    	/// <summary>
    	/// 指针操作接口
    	/// </summary>
    	public interface IPointerCall
    	{
    		/// <summary>
    		/// 指针操作
    		/// </summary>
    		/// <param name="p">源指针</param>
    		/// <returns>修改后指针</returns>
    		unsafe byte* Ptr(byte* p);
    	}
    
    #region 非泛型
    	/// <summary>
    	/// [非泛型] 指针操作基类
    	/// </summary>
    	public abstract class PointerCall : IPointerCall
    	{
    		public abstract unsafe byte* Ptr(byte* p);
    	}
    
    	/// <summary>
    	/// [非泛型] 指针操作派生类: 指针+偏移
    	/// </summary>
    	public sealed class PointerCallAdd : PointerCall
    	{
    		/// <summary>
    		/// 偏移值
    		/// </summary>
    		public int Offset = 0;
    
    		public override unsafe byte* Ptr(byte* p)
    		{
    			return unchecked(p + Offset);
    		}
    	}
    
    	/// <summary>
    	/// [非泛型] 指针操作密封类: 指针+偏移
    	/// </summary>
    	public sealed class SldPointerCallAdd : IPointerCall
    	{
    		/// <summary>
    		/// 偏移值
    		/// </summary>
    		public int Offset = 0;
    
    		public unsafe byte* Ptr(byte* p)
    		{
    			return unchecked(p + Offset);
    		}
    	}
    
    	/// <summary>
    	/// [非泛型] 指针操作结构体: 指针+偏移
    	/// </summary>
    	public struct SPointerCallAdd : IPointerCall
    	{
    		/// <summary>
    		/// 偏移值
    		/// </summary>
    		public int Offset;
    
    		public unsafe byte* Ptr(byte* p)
    		{
    			return unchecked(p + Offset);
    		}
    	}
    
    #endregion
    
    #region 泛型
    	// !!! C#不支持将整数类型作为泛型约束 !!!
    	//public abstract class GenPointerCall<T> : IPointerCall where T: int, long
    	//{
    	//    public abstract unsafe byte* Ptr(byte* p);
    
    	//    void d()
    	//    {
    	//    }
    	//}
    
    #endregion
    
    #region 全部测试
    	/// <summary>
    	/// 指针操作的一些常用函数
    	/// </summary>
    	public static class PointerCallTool
    	{
    #if DEBUG
    		private const int CountLoop = 10000000;	// 循环次数
    #else
    		private const int CountLoop = 200000000;	// 循环次数
    #endif
    
    		/// <summary>
    		/// 调用指针操作
    		/// </summary>
    		/// <typeparam name="T">具有IPointerCall接口的类型。</typeparam>
    		/// <param name="ptrcall">调用者</param>
    		/// <param name="p">源指针</param>
    		/// <returns>修改后指针</returns>
    		public static unsafe byte* CallPtr<T>(T ptrcall, byte* p) where T : IPointerCall
    		{
    			return ptrcall.Ptr(p);
    		}
    		public static unsafe byte* CallClassPtr<T>(T ptrcall, byte* p) where T : PointerCall
    		{
    			return ptrcall.Ptr(p);
    		}
    		public static unsafe byte* CallRefPtr<T>(ref T ptrcall, byte* p) where T : IPointerCall
    		{
    			return ptrcall.Ptr(p);
    		}
    
    		// C#不允许将特定的结构体作为泛型约束。所以对于结构体只能采用上面那个方法,通过IPointerCall接口进行约束,可能会造成性能下降。
    		//public static unsafe byte* SCallPtr<T>(T ptrcall, byte* p) where T : SPointerCallAdd
    		//{
    		//    return ptrcall.Ptr(p);
    		//}
    
    		private static int TryIt_Static_Offset;
    		private static unsafe byte* TryIt_Static_Ptr(byte* p)
    		{
    			return unchecked(p + TryIt_Static_Offset);
    		}
    		/// <summary>
    		/// 执行测试 - 静态调用
    		/// </summary>
    		/// <param name="sOut">文本输出</param>
    		private static unsafe void TryIt_Static(StringBuilder sOut, int CountLoop)
    		{
    			TryIt_Static_Offset = 1;
    
    			// == 性能测试 ==
    			byte* p = null;
    			Stopwatch sw = new Stopwatch();
    			int i;
    			unchecked
    			{
    				#region 测试
    				// 硬编码.栈变量
    				int iOffset = 1;
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = p + iOffset;
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("硬编码.栈变量:\t{0}", sw.ElapsedMilliseconds));
    
    				// 硬编码.栈分配
    				int* pOffset = stackalloc int[1];
    				pOffset[0] = 1;
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = p + pOffset[0];
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("硬编码.栈分配:\t{0}", sw.ElapsedMilliseconds));
    
    				// 硬编码.静态
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = p + TryIt_Static_Offset;
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("硬编码.静态:\t{0}", sw.ElapsedMilliseconds));
    
    				// 静态调用
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = TryIt_Static_Ptr(p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("静态调用:\t{0}", sw.ElapsedMilliseconds));
    
    				#endregion // 测试
    			}
    		}
    
    		private static long TryIt_Static64_Offset;
    		private static unsafe byte* TryIt_Static64_Ptr(byte* p)
    		{
    			return unchecked(p + TryIt_Static64_Offset);
    		}
    		/// <summary>
    		/// 执行测试 - 静态调用
    		/// </summary>
    		/// <param name="sOut">文本输出</param>
    		private static unsafe void TryIt_Static64(StringBuilder sOut, int CountLoop)
    		{
    			TryIt_Static64_Offset = 1;
    
    			// == 性能测试 ==
    			byte* p = null;
    			Stopwatch sw = new Stopwatch();
    			int i;
    			unchecked
    			{
    				#region 测试
    				// 硬编码.栈变量
    				long iOffset = 1;
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = p + iOffset;
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("64硬编码.栈变量:\t{0}", sw.ElapsedMilliseconds));
    
    				// 硬编码.栈分配
    				long* pOffset = stackalloc long[1];
    				pOffset[0] = 1;
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = p + pOffset[0];
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("64硬编码.栈分配:\t{0}", sw.ElapsedMilliseconds));
    
    				// 硬编码.静态
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = p + TryIt_Static64_Offset;
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("64硬编码.静态:\t{0}", sw.ElapsedMilliseconds));
    
    				// 静态调用
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = TryIt_Static64_Ptr(p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("64静态调用:\t{0}", sw.ElapsedMilliseconds));
    
    				#endregion // 测试
    			}
    		}
    
    		/// <summary>
    		/// 执行测试 - 非泛型
    		/// </summary>
    		/// <param name="sOut">文本输出</param>
    		private static unsafe void TryIt_NoGen(StringBuilder sOut, int CountLoop)
    		{
    			// 创建
    			PointerCallAdd pca = new PointerCallAdd();
    			SldPointerCallAdd dpca = new SldPointerCallAdd();
    			SPointerCallAdd spca;
    			pca.Offset = 1;
    			spca.Offset = 1;
    
    			// 转型
    			PointerCall pca_base = pca;
    			IPointerCall pca_itf = pca;
    			IPointerCall dpca_itf = dpca;
    			IPointerCall spca_itf = spca;
    
    			// == 性能测试 ==
    			byte* p = null;
    			Stopwatch sw = new Stopwatch();
    			int i;
    			unchecked
    			{
    				#region 调用
    				#region 直接调用
    				// 调用派生类
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = pca.Ptr(p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("调用派生类:\t{0}", sw.ElapsedMilliseconds));
    
    				// 调用密封类
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = dpca.Ptr(p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("调用密封类:\t{0}", sw.ElapsedMilliseconds));
    
    				// 调用结构体
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = spca.Ptr(p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("调用结构体:\t{0}", sw.ElapsedMilliseconds));
    
    				#endregion	// 直接调用
    
    				#region 间接调用
    				// 调用基类
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = pca_base.Ptr(p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("调用基类:\t{0}", sw.ElapsedMilliseconds));
    
    				// 调用派生类的接口
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = pca_itf.Ptr(p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("调用派生类的接口:\t{0}", sw.ElapsedMilliseconds));
    
    				// 调用密封类的接口
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = dpca_itf.Ptr(p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("调用密封类的接口:\t{0}", sw.ElapsedMilliseconds));
    
    				// 调用结构体的接口
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = spca_itf.Ptr(p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("调用结构体的接口:\t{0}", sw.ElapsedMilliseconds));
    
    				#endregion	// 间接调用
    
    				#endregion	// 调用
    
    				#region 泛型调用
    
    				#region 泛型基类约束
    				// 基类泛型调用派生类
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = CallClassPtr(pca, p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("基类泛型调用派生类:\t{0}", sw.ElapsedMilliseconds));
    
    				// 基类泛型调用基类
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = CallClassPtr(pca_base, p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("基类泛型调用基类:\t{0}", sw.ElapsedMilliseconds));
    
    				#endregion // 泛型基类约束
    
    				#region 泛型接口约束 - 直接调用
    				// 接口泛型调用派生类
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = CallPtr(pca, p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("接口泛型调用派生类:\t{0}", sw.ElapsedMilliseconds));
    
    				// 接口泛型调用密封类
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = CallPtr(dpca, p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("接口泛型调用密封类:\t{0}", sw.ElapsedMilliseconds));
    
    				// 接口泛型调用结构体
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = CallPtr(spca, p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("接口泛型调用结构体:\t{0}", sw.ElapsedMilliseconds));
    
    				// 接口泛型调用结构体引用
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = CallRefPtr(ref spca, p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("接口泛型调用结构体引用:\t{0}", sw.ElapsedMilliseconds));
    
    				#endregion	// 直接调用
    
    				#region 间接调用
    				// 接口泛型调用基类
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = CallPtr(pca_base, p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("接口泛型调用基类:\t{0}", sw.ElapsedMilliseconds));
    
    				// 接口泛型调用派生类的接口
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = CallPtr(pca_itf, p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("接口泛型调用派生类的接口:\t{0}", sw.ElapsedMilliseconds));
    
    				// 接口泛型调用密封类的接口
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = CallPtr(dpca_itf, p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("接口泛型调用密封类的接口:\t{0}", sw.ElapsedMilliseconds));
    
    				// 接口泛型调用结构体的接口
    				sw.Reset();
    				sw.Start();
    				for (i = 0; i < CountLoop; ++i)
    				{
    					p = CallPtr(spca_itf, p);
    				}
    				sw.Stop();
    				sOut.AppendLine(string.Format("接口泛型调用结构体的接口:\t{0}", sw.ElapsedMilliseconds));
    
    				#endregion	// 间接调用
    
    				#endregion	// 泛型调用
    
    			}
    		}
    
    		/// <summary>
    		/// 执行测试 - 泛型
    		/// </summary>
    		/// <param name="sOut">文本输出</param>
    		private static unsafe void TryIt_Gen(StringBuilder sOut, int CountLoop)
    		{
    			// !!! C#不支持将整数类型作为泛型约束 !!!
    		}
    
    		/// <summary>
    		/// 执行测试
    		/// </summary>
    		public static string TryIt()
    		{
    			StringBuilder sOut = new StringBuilder();
    			sOut.AppendLine("== PointerCallTool.TryIt() ==");
    			TryIt_Static(sOut, CountLoop);
    			TryIt_Static64(sOut, CountLoop);
    			TryIt_NoGen(sOut, CountLoop);
    			TryIt_Gen(sOut, CountLoop);
    			sOut.AppendLine();
    			return sOut.ToString();
    		}
    
    		/// <summary>
    		/// 执行测试 - static
    		/// </summary>
    		public static string TryItStatic()
    		{
    			StringBuilder sOut = new StringBuilder();
    			int cnt = CountLoop * 10;
    			sOut.AppendLine("== PointerCallTool.TryItStatic() ==");
    			TryIt_Static(sOut, cnt);
    			TryIt_Static64(sOut, cnt);
    			sOut.AppendLine();
    			return sOut.ToString();
    		}
    	}
    #endregion
    
    }
    

    二、测试环境


      编译器——
    VS2005:Visual Studio 2005 SP1。
    VS2010:Visual Studio 2010 SP1。
    采用上述编译器编译为Release版程序,最大速度优化。

      机器A——
    HP CQ42-153TX
    处理器:Intel Core i5-430M(2.26GHz, Turbo 2.53GHz, 3MB L3)
    内存容量:2GB (DDR3-1066)


      机器B——
    DELL Latitude E6320 
    处理器:Intel i3-2310M(2.1GHz, 3MB L3)
    内存容量:4GB (DDR3-1333,双通道)


      测试环境——
    A_2005:机器A,VS2005,Window 7 32位。
    A_2010:机器A,VS2010,Window 7 32位。
    B_2005:机器B,VS2005,Window XP SP3 32位。
    B_2010:机器B,VS2010,Window XP SP3 32位。
    B64_2005:机器B,VS2005,Window 7 64位(x64)。
    B64_2010:机器B,VS2010,Window 7 64位(x64)。




    三、硬编码与静态调用 的测试结果(栈变量、栈分配、64位整数)



      因为硬编码与静态调用很可能会被执行函数展开优化,速度明显比其他测试项目要快。所我另外写了一个测试函数(TryItStatic),将循环次数设为原来的10倍。


      测试结果如下(单位:毫秒)——

    模式A_2005A_2010B_2005B_2010B64_2005B64_2010
    硬编码.栈变量: 1608 1623 957 966 960 959
    硬编码.栈分配: 1612 1617 1073 957 961 960
    硬编码.静态: 1609 1613 957 971 961 960
    静态调用: 1608 1611 1063 958 961 963
    64硬编码.栈变量: 1610 1617 967 957 959 1010
    64硬编码.栈分配: 1610 1619 1034 957 960 1012
    64硬编码.静态: 1609 1618 999 996 957 1010
    64静态调用: 1610 1615 959 1002 957 7696




      结果分析——
    先看32位与64位的区别。发现在大多数情况,32位与64位的速度是一样的。唯一就是64位整数运算代码在“64位平台+VS2010”上运行时,速度比在32位下还慢,尤其是静态调用慢了好几倍,硬编码代码的速度也有所下降。真的很奇怪,既然运行的是同一份程序,为什么64位比32位还慢,难道是.Net 4.0在x64平台上的即时编译器的问题?不解。
    栈变量、栈分配、静态变量的访问速度几乎一致,看来可以放心地随意使用。


      看来以后写指针操作代码时,只写64位整数版就行了。




    四、密封类 的测试结果



      测试结果如下(单位:毫秒)——
    模式 A_2005 A_2010 B_2005 B_2010 B64_2005B64_2010
    硬编码.栈变量: 162 162 95 95 96 95
    硬编码.栈分配: 161 161 95 95 95 97
    硬编码.静态: 161 165 97 95 97 95
    静态调用: 161 163 95 95 96 97
    64硬编码.栈变量: 161161989596 100
    64硬编码.栈分配: 160162959795 100
    64硬编码.静态: 162 162 95 97 95 100
    64静态调用: 161 161 95 95 97 770
    调用派生类: 563 568 670 668 676 580
    调用密封类: 161 162 101 103 102 767
    调用结构体: 163 161 116 102 191 772
    调用基类: 566 573 668 660 675 577
    调用派生类的接口: 727 731 767 862 862 770
    调用密封类的接口: 721 730 957 862 870 771
    调用结构体的接口: 104511341318134013441253
    基类泛型调用派生类: 910795127478912561287
    基类泛型调用基类: 902 785 1092 676 1346 1250
    接口泛型调用派生类: 1407733163486216331633
    接口泛型调用密封类: 1405808173395617431638
    接口泛型调用结构体: 5661606711018641250
    接口泛型调用结构体引用: 48016170098769961
    接口泛型调用基类: 1409728176776416311635
    接口泛型调用派生类的接口: 1410727170296617301634
    接口泛型调用密封类的接口: 1402808171995816351637
    接口泛型调用结构体的接口: 161711281859149922082117

      将测试结果重新排版一下,突出不同实现方法的速度区别——


    环境分类基类派生类密封类结构体结构体的引用
    A_2005 直接调用 566 563 161 163  
      接口调用   727 721 1045  
      基类约束泛型调用 902 910      
      接口约束泛型调用   1407 1405 566 480
      接口约束泛型调用接口 1409 1410 1402 1617  
    A_2010 直接调用 573 568 162 161  
      接口调用   731 730 1134  
      基类约束泛型调用 785 795      
      接口约束泛型调用   733 808 160 161
      接口约束泛型调用接口 728 727 808 1128  
    B_2005 直接调用 668 670 101 116  
      接口调用   767 957 1318  
      基类约束泛型调用 1092 1274      
      接口约束泛型调用   1634 1733 671 700
      接口约束泛型调用接口 1767 1702 1719 1859  
    B_2010 直接调用 660 668 103 102  
      接口调用   862 862 1340  
      基类约束泛型调用 676 789      
      接口约束泛型调用   862 956 101 98
      接口约束泛型调用接口 764 966 958 1499  
    B64_2005 直接调用 675 676 102 191  
      接口调用   862 870 1344  
      基类约束泛型调用 1346 1256      
      接口约束泛型调用   1633 1743 864 769
      接口约束泛型调用接口 1631 1730 1635 2208  
    B64_2010 直接调用 577 580 767 772  
      接口调用   770 771 1253  
      基类约束泛型调用 1250 1287      
      接口约束泛型调用   1633 1638 1250 961
      接口约束泛型调用接口 1635 1634 1637 2117  



      综合来看,密封类的性能最好,在大多数测试项目中名列前茅——
    “直接调用”时能被内联(inline)优化,与“硬编码”一样快,快于派生类。
    “接口调用”、“泛型调用接口”时与派生类性能一致,快于结构体的“接口调用”。
    唯一就是在“泛型调用”时,落后于结构体,与派生类差不多稍微慢一点。
    再就是奇怪的“64位平台+VS2010”问题,密封类、结构体在直接调用时,还不如派生类。


      最后总结一下可能会被内联优化的调用类型——
    32位平台+VS2005:调用密封类、调用结构体。
    32位平台+VS2010:调用密封类、调用结构体、接口约束泛型调用结构体。
    64位平台+VS2005:调用密封类、调用结构体。
    64位平台+VS2010:(无)。




    (完)



    测试程序exe——
    http://115.com/file/e6ymd5fe
    http://download.csdn.net/detail/zyl910/3619643


    源代码下载——
    http://115.com/file/aqz167n9
    http://download.csdn.net/detail/zyl910/3619647


    目录——
    C#类与结构体究竟谁快——各种函数调用模式速度评测:http://www.cnblogs.com/zyl910/archive/2011/09/19/2186623.html
    再探C#类与结构体究竟谁快——考虑栈变量、栈分配、64位整数、密封类:http://www.cnblogs.com/zyl910/archive/2011/09/20/2186622.html
    三探C#类与结构体究竟谁快——MSIL(微软中间语言)解读:http://www.cnblogs.com/zyl910/archive/2011/09/24/2189403.html
    四探C#类与结构体究竟谁快——跨程序集(assembly)调用:http://www.cnblogs.com/zyl910/archive/2011/10/01/2197844.html

  • 相关阅读:
    synchronous_commit 参数的再次说明
    ubuntu 16.04 + zabbix 3.4 + postgresql pg_monz
    ubuntu 16.04 + zabbix 3.4 + postgresql shell
    ubuntu 16.04 + zabbix 3.4 + postgresql UserParameter
    ubuntu 16.04 + zabbix 3.4 + postgresql libzbxpgsql
    ubuntu 16.04 + zabbix 3.4 + zabbix agent
    ubuntu 16.04 + zabbix 3.4 + zabbix proxy
    ubuntu 16.04 + zabbix 3.4 + zabbix server
    apt-get、apt-cache的一些日常操作
    ubuntu 16.04 apt-get source 替换为 aliyun
  • 原文地址:https://www.cnblogs.com/zyl910/p/2186622.html
Copyright © 2011-2022 走看看