메소드의 실행 시간을 정확히 밀리 초 단위로 기록하는 방법은 무엇입니까?


222

메소드 실행 시간 (밀리 초)을 판별하는 방법이 있습니까?


2
더 빨리 최적화하기 위해 무엇을 최적화 할 수 있는지 알고 싶습니까?
Mike Dunlavey 님이

1
예, 일부 페이지를로드하는 UIWebView를 사용하고 있습니다. 메소드가 페이지 1에서 페이지 10까지로드하는 데 필요한 시간을 확인하여 페이지로드를 최적화하고 싶습니다.
dan

2
이것은이 질문과 중복되는 것 같습니다 : stackoverflow.com/questions/889380/…
Brad Larson

@BradLarson 중복 된 것으로 보이지만 다른 질문에 더 나은 답변이 있습니다. 즉, 눈에 띄는 답변이 (잘못된) NSDate를 사용하도록 제안하지 않고 대신 NSDate가 왜이 목적을 위해 잘못된 방법을 사용하는지 설명합니다.
Thomas Tempelmann

답변:


437
NSDate *methodStart = [NSDate date];

/* ... Do whatever you need to do ... */

NSDate *methodFinish = [NSDate date];
NSTimeInterval executionTime = [methodFinish timeIntervalSinceDate:methodStart];
NSLog(@"executionTime = %f", executionTime);

빠른:

let methodStart = NSDate()

/* ... Do whatever you need to do ... */

let methodFinish = NSDate()
let executionTime = methodFinish.timeIntervalSinceDate(methodStart)
print("Execution time: \(executionTime)")

스위프트 3 :

let methodStart = Date()

/* ... Do whatever you need to do ... */

let methodFinish = Date()
let executionTime = methodFinish.timeIntervalSince(methodStart)
print("Execution time: \(executionTime)")

사용하기 쉽고 1 밀리 초 미만의 정밀도를 갖습니다.


3
@PeterWarbo NSTimeInterval은 double의 typedef이며 초로 정의됩니다 ( developer.apple.com/library/mac/#documentation/Cocoa/Reference/…
Ben Lings

5
이 값을 % f-NSLog ( "executionTime = % f", executionTime)로 기록 할 수 있습니다.
Tony

1
@Tony 당신은 @를 잊었다NSLog(@"executionTime = %f", executionTime);
John Riselvato

6
난 그냥 비교 NSDatemach_absolute_time()주변에서 수준을 30ms의. 27 대 29, 36 대 39, 43 대 45 NSDate는 나에게 사용하기 쉬웠으며 결과는 귀찮게하지 않을 정도로 유사했습니다 mach_absolute_time().
nevan king

5
NSDate를 기반으로하는 것은 시간이 뒤로 이동하더라도 시간이 지나갈 수 있으므로 통과 시간을 측정하는 데 안전하지 않습니다. 훨씬 더 안전한 방법은 mach_absolute_time을 사용하는 것입니다. 여기 다른 많은 답변에서 볼 수 있습니다. 이것은 나쁜 예가되기 위해 하향 조정되어야합니다. : 또한 더 자세히이 모든 설명은 관련 답변을 참조 stackoverflow.com/a/30363702/43615
토마스 TEMPELMANN

252

내가 사용하는 두 개의 단선 매크로는 다음과 같습니다.

#define TICK   NSDate *startTime = [NSDate date]
#define TOCK   NSLog(@"Time: %f", -[startTime timeIntervalSinceNow])

다음과 같이 사용하십시오.

TICK;

/* ... Do Some Work Here ... */

TOCK;

13
ㅋ. 나는 그것을 좋아한다!
bobmoff

5
이것이 좋은 이유는 진드기가 거의 기록 할 필요가 없다는 기억에 남는 문구라는 것입니다.
John Riselvato

30
#define TOCK NSLog(@"%s Time: %f", __func__, -[startTime timeIntervalSinceNow])이 답변은 타이머가 사용 된 기능을 반환합니다. TICK TOCK을 사용하여 여러 기능의 시간을 정하면 유용합니다.
golmschenk

3
좋은 아이디어 @golmschenk! 당신은 또한에 볼 수 __PRETTY_FUNCTION____LINE__더 자세한 정보를 원하는 경우.
Ron

50

OS X에서 세밀한 타이밍을 위해서는 다음에서 mach_absolute_time( )선언 해야합니다 <mach/mach_time.h>.

#include <mach/mach_time.h>
#include <stdint.h>

// Do some stuff to setup for timing
const uint64_t startTime = mach_absolute_time();
// Do some stuff that you want to time
const uint64_t endTime = mach_absolute_time();

// Time elapsed in Mach time units.
const uint64_t elapsedMTU = endTime - startTime;

// Get information for converting from MTU to nanoseconds
mach_timebase_info_data_t info;
if (mach_timebase_info(&info))
   handleErrorConditionIfYoureBeingCareful();

// Get elapsed time in nanoseconds:
const double elapsedNS = (double)elapsedMTU * (double)info.numer / (double)info.denom;

물론 세밀한 측정에 대한 일반적인주의 사항이 적용됩니다. 테스트중인 루틴을 여러 번 호출하고 최소 / 일부 다른 형식의 처리를 평균화 / 취득하는 것이 가장 좋습니다.

또한 Shark와 같은 도구를 사용하여 실행중인 응용 프로그램 을 프로파일 링 하는 것이 더 유용 할 수 있습니다 . 정확한 타이밍 정보를 제공하지는 않지만 응용 프로그램 시간의 몇 퍼센트 를 어디에서 소비하고 있는지 알려주는 경우가 종종 있습니다 (항상 그런 것은 아님).


1
이것을 스위프트에서 작동 시키려고 노력 중입니다 ... 어떤 제안?
zumzum

1
"하나는하지 않습니다 단순히 ... 스위프트로 변환"- 네드 스타크
stonedauwg

@zumzum Swift 에서이 작업을 수행하는 예는 내 대답을 참조하십시오.
jbg

23

편리한 래퍼가 있습니다 mach_absolute_time()– 그것은 CACurrentMediaTime()기능입니다.

달리 NSDate또는 CFAbsoluteTimeGetCurrent()오프셋 mach_absolute_time()CACurrentMediaTime()같은 시간대 서머 또는 윤초 의한 것과 외부 시간 기준의 변화에 내부 호스트 클럭, 정확한, 단원 자 계수 아닌 대상에 기초한다.


ObjC

CFTimeInterval startTime = CACurrentMediaTime();
// Do your stuff here
CFTimeInterval endTime = CACurrentMediaTime();
NSLog(@"Total Runtime: %g s", endTime - startTime);

빠른

let startTime = CACurrentMediaTime()
// Do your stuff here
let endTime = CACurrentMediaTime()
print("Total Runtime: \(endTime - startTime) s")

3
나는이 답변에 더 많은 찬사를받을 가치가 있다고 생각한다. 를 사용하는 것보다 낫습니다 NSDate.
평균 Joe

12

Swift에서는 다음을 사용하고 있습니다.

내 Macros.swift에서 방금 추가했습니다.

var startTime = NSDate()
func TICK(){ startTime =  NSDate() }
func TOCK(function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__){
    println("\(function) Time: \(startTime.timeIntervalSinceNow)\nLine:\(line) File: \(file)")
}

이제 어디서나 전화 할 수 있습니다

TICK()

// your code to be tracked

TOCK()
  • 이 코드는 Ron의 코드를 기반으로 Swift로 변환됩니다.
  • 전 세계에서 시작일을 사용하고 있습니다. 개선 할 제안은 환영합니다.

이것은 \(-startTime.timeIntervalSinceNow)(음수에 유의하십시오)
Snowman

9

나는 이것이 오래된 것임을 알고 있지만 심지어 그것을 지나쳐 돌아 다니는 것을 알았으므로 여기에서 내 옵션을 제출할 것이라고 생각했습니다.

가장 좋은 방법은 이것에 대한 내 블로그 게시물을 확인하는 것입니다 : Objective-C의 타이밍 : 스톱워치

기본적으로 나는 매우 기본적인 방식으로 시청을 중단하지만 캡슐화되어 다음을 수행 해야하는 수업을 작성했습니다.

[MMStopwatchARC start:@"My Timer"];
// your work here ...
[MMStopwatchARC stop:@"My Timer"];

그리고 당신은 결국 :

MyApp[4090:15203]  -> Stopwatch: [My Timer] runtime: [0.029]

로그에 ...

다시 한 번, 내 게시물을 좀 더 확인하거나 여기에서 다운로드하십시오 : MMStopwatch.zip


7

Ron의 솔루션을 기반으로 매크로를 사용합니다 .

#define TICK(XXX) NSDate *XXX = [NSDate date]
#define TOCK(XXX) NSLog(@"%s: %f", #XXX, -[XXX timeIntervalSinceNow])

코드 줄의 경우 :

TICK(TIME1);
/// do job here
TOCK(TIME1);

콘솔에서 다음과 같은 것을 볼 수 있습니다 : TIME1 : 0.096618


당신의 대답은 Ron의 대답과 크게 다르지 않으며 어떻게 든 그것이 더 나은 방법으로 보지 못합니까?
Trilarion

2
한 컨텍스트 내에서 @Ron의 솔루션을 두 번 사용할 수 없습니다. 이것이이 매크로의 주된 이유입니다.
Sergey Teryokhin 2016 년

4

이 블로그 게시물의 코드에서 영감을 얻은 최소한의 단일 페이지 클래스 구현을 사용합니다 .

#import <mach/mach_time.h>

@interface DBGStopwatch : NSObject

+ (void)start:(NSString *)name;
+ (void)stop:(NSString *)name;

@end

@implementation DBGStopwatch

+ (NSMutableDictionary *)watches {
    static NSMutableDictionary *Watches = nil;
    static dispatch_once_t OnceToken;
    dispatch_once(&OnceToken, ^{
        Watches = @{}.mutableCopy;
    });
    return Watches;
}

+ (double)secondsFromMachTime:(uint64_t)time {
    mach_timebase_info_data_t timebase;
    mach_timebase_info(&timebase);
    return (double)time * (double)timebase.numer /
        (double)timebase.denom / 1e9;
}

+ (void)start:(NSString *)name {
    uint64_t begin = mach_absolute_time();
    self.watches[name] = @(begin);
}

+ (void)stop:(NSString *)name {
    uint64_t end = mach_absolute_time();
    uint64_t begin = [self.watches[name] unsignedLongLongValue];
    DDLogInfo(@"Time taken for %@ %g s",
              name, [self secondsFromMachTime:(end - begin)]);
    [self.watches removeObjectForKey:name];
}

@end

사용법은 매우 간단합니다.

  • [DBGStopwatch start:@"slow-operation"];처음에 전화 해
  • 그런 다음 [DBGStopwatch stop:@"slow-operation"];시간을 얻기 위해 완료 후

3

이 StopWatch 클래스를 사용 하면 정말 훌륭한 타이밍 (초. 초)을 얻을 수 있습니다 . iPhone에서 고정밀 타이머를 사용합니다. NSDate를 사용하면 두 번째 정확도 만 얻을 수 있습니다. 이 버전은 자동 릴리스 및 objective-c를 위해 특별히 설계되었습니다. 필요한 경우 C ++ 버전도 있습니다. 여기서 C ++ 버전을 찾을 수 있습니다 .

StopWatch.h

#import <Foundation/Foundation.h>


@interface StopWatch : NSObject 
{
    uint64_t _start;
    uint64_t _stop;
    uint64_t _elapsed;
}

-(void) Start;
-(void) Stop;
-(void) StopWithContext:(NSString*) context;
-(double) seconds;
-(NSString*) description;
+(StopWatch*) stopWatch;
-(StopWatch*) init;
@end

StopWatch.m

#import "StopWatch.h"
#include <mach/mach_time.h>

@implementation StopWatch

-(void) Start
{
    _stop = 0;
    _elapsed = 0;
    _start = mach_absolute_time();
}
-(void) Stop
{
    _stop = mach_absolute_time();   
    if(_stop > _start)
    {
        _elapsed = _stop - _start;
    }
    else 
    {
        _elapsed = 0;
    }
    _start = mach_absolute_time();
}

-(void) StopWithContext:(NSString*) context
{
    _stop = mach_absolute_time();   
    if(_stop > _start)
    {
        _elapsed = _stop - _start;
    }
    else 
    {
        _elapsed = 0;
    }
    NSLog([NSString stringWithFormat:@"[%@] Stopped at %f",context,[self seconds]]);

    _start = mach_absolute_time();
}


-(double) seconds
{
    if(_elapsed > 0)
    {
        uint64_t elapsedTimeNano = 0;

        mach_timebase_info_data_t timeBaseInfo;
        mach_timebase_info(&timeBaseInfo);
        elapsedTimeNano = _elapsed * timeBaseInfo.numer / timeBaseInfo.denom;
        double elapsedSeconds = elapsedTimeNano * 1.0E-9;
        return elapsedSeconds;
    }
    return 0.0;
}
-(NSString*) description
{
    return [NSString stringWithFormat:@"%f secs.",[self seconds]];
}
+(StopWatch*) stopWatch
{
    StopWatch* obj = [[[StopWatch alloc] init] autorelease];
    return obj;
}
-(StopWatch*) init
{
    [super   init];
    return self;
}

@end

이 클래스에는 자동 해제 된 stopWatch객체를 반환하는 정적 메서드가 있습니다.

를 호출 start하면 seconds메소드를 사용 하여 경과 시간을 얻습니다. start다시 전화를 걸어 다시 시작하십시오. 아니면 stop중지하십시오. 전화 seconds한 후에도 언제든지 시간 (전화 )을 읽을 수 있습니다 stop.

함수 예 (타이밍 실행 호출)

-(void)SomeFunc
{
   StopWatch* stopWatch = [StopWatch stopWatch];
   [stopWatch Start];

   ... do stuff

   [stopWatch StopWithContext:[NSString stringWithFormat:@"Created %d Records",[records count]]];
}

"초만 정확도"가 잘못되었습니다. NSTimeInterval의 전체 부분은 초이지만 두 배입니다.
Steven Fisher

3

이 코드를 사용합니다 :

#import <mach/mach_time.h>

float TIME_BLOCK(NSString *key, void (^block)(void)) {
    mach_timebase_info_data_t info;
    if (mach_timebase_info(&info) != KERN_SUCCESS)
    {
        return -1.0;
    }

    uint64_t start = mach_absolute_time();
    block();
    uint64_t end = mach_absolute_time();
    uint64_t elapsed = end - start;

    uint64_t nanos = elapsed * info.numer / info.denom;
    float cost = (float)nanos / NSEC_PER_SEC;

    NSLog(@"key: %@ (%f ms)\n", key, cost * 1000);
    return cost;
}

2

나는 이것을 사용한다 :

clock_t start, end;
double elapsed;
start = clock();

//Start code to time

//End code to time

end = clock();
elapsed = ((double) (end - start)) / CLOCKS_PER_SEC;
NSLog(@"Time: %f",elapsed);

그러나 iPhone의 CLOCKS_PER_SEC에 대해 잘 모르겠습니다. 당신은 그것을 떠나고 싶을 수도 있습니다.


2
iPhone의 CLOCKS_PER_SEC는 매우 부정확 한 값입니다.
mxcl

1
알아 둘만 한. 지금이 작업을 수행해야한다면 Matthew의 대답을 사용하십시오.
David Kanarek 2016 년

2

mach_absolute_time()Swift 4를 사용한 세밀한 타이밍의 예 :

let start = mach_absolute_time()

// do something

let elapsedMTU = mach_absolute_time() - start
var timebase = mach_timebase_info()
if mach_timebase_info(&timebase) == 0 {
    let elapsed = Double(elapsedMTU) * Double(timebase.numer) / Double(timebase.denom)
    print("render took \(elapsed)")
}
else {
    print("timebase error")
}

2

더 빠른 목표를 위해 고칠 수있는 것을 찾는 것이 목표라면 조금 다른 목표입니다. 함수가 수행하는 시간을 측정하는 것은 당신이 차이를 낸 것이 무엇인지 알아내는 방법 이지만, 무엇을해야하는지 알아내는 것은 다른 기술이 필요합니다. 이것이 내가 추천 하는 것입니다 .iPhone에서 할 수 있다는 것을 알고 있습니다.

편집 : 리뷰어는 대답을 정교하게 제안 했으므로 간단한 대답 방법을 생각하려고합니다.
전체 프로그램은 귀찮게하기 위해 충분한 시간이 걸립니다. N 초 라고 가정하십시오 .
속도를 높일 수 있다고 가정합니다. 당신이 할 수있는 유일한 방법은 m 초를 고려하여 그 시간에 무언가를하지 않는 것 입니다.
당신은 처음에 그 것이 무엇인지 모른다. 모든 프로그래머와 마찬가지로 추측 할 수 있지만 쉽게 다른 것일 수 있습니다. 그것이 무엇이든, 그것을 찾는 방법은 다음과 같습니다.

그 것이 무엇이든간에 시간의 분수 m / N 을 고려하기 때문에 무작위로 일시 중지하면 확률은 m / N 이며 그 일을 수행 할 때 잡을 수 있습니다. 물론 다른 일을하고있을 수도 있지만 잠시 멈추고 무슨 일을하는지보십시오.
이제 다시 해봐 다시 같은 일을한다면 더 의심 스러울 수 있습니다.

10 회 또는 20 회를 수행하십시오. 이제 여러 번의 일시 정지에서 특정 작업 (어떻게 설명하든 상관 없음)을 수행하여 제거 할 수 있으면 두 가지를 알 수 있습니다. 시간이 어느 정도 걸리는지는 대략 알지만 정확히 무엇을 고칠지는 알고 있습니다. 얼마나 많은 시간이 절약되는지 정확하게
알고 싶다면 간단합니다. 전에 측정하고 수정 한 후 측정하십시오. 당신이 정말로 실망한다면, 수정을 철회하십시오.

이것이 측정과 어떻게 다른지 보십니까? 그건 측정되지 발견 . 대부분의 프로파일 링은 시간이 얼마나 걸리는지를 가능한 한 정확하게 측정하는 것이 중요하며, 수정해야 할 사항을 식별하는 데 문제가 있습니다. 프로파일 링으로 모든 문제를 찾을 수는 없지만이 방법으로 모든 문제를 찾아 낼 수 있으며, 이것이 아프지 않은 문제입니다.


0

Swift에서 defer 키워드를 사용하여 다른 방법을 사용하십시오.

func methodName() {
  let methodStart = Date()
  defer {
    let executionTime = Date().timeIntervalSince(methodStart)
    print("Execution time: \(executionTime)")
  }
  // do your stuff here
}

Apple의 문서에서 : defer 문은 defer 문이 나타나는 범위 밖으로 프로그램 제어를 전송하기 직전에 코드를 실행하는 데 사용됩니다.

이것은 관련 코드를 그룹화한다는 이점이있는 try / finally 블록과 유사합니다.


0

나는 이것을 utils library ( Swift 4.2 )에서 사용합니다 :

public class PrintTimer {
    let start = Date()
    let name: String

    public init(file: String=#file, line: Int=#line, function: String=#function, name: String?=nil) {
        let file = file.split(separator: "/").last!
        self.name = name ?? "\(file):\(line) - \(function)"
    }

    public func done() {
        let end = Date()
        print("\(self.name) took \((end.timeIntervalSinceReferenceDate - self.start.timeIntervalSinceReferenceDate).roundToSigFigs(5)) s.")
    }
}

... 다음과 같은 메소드를 호출하십시오.

func myFunctionCall() {
    let timer = PrintTimer()
    // ...
    timer.done()
}

... 실행 후 콘솔에서 다음과 같이 보입니다.

MyFile.swift:225 - myFunctionCall() took 1.8623 s.

위의 TICK / TOCK만큼 간결하지는 않지만 수행중인 작업을 볼 수있을만큼 명확하며 시간이 초과 된 작업 (파일, 메서드 시작 줄 및 함수 이름)을 자동으로 포함합니다. 더 자세한 정보를 원한다면 (예를 들어, 일반적인 경우처럼 메소드 호출을 타이밍하는 것이 아니라 해당 메소드 내의 블록을 타이밍하는 경우) PrintTimer init에 "name ="Foo ""매개 변수를 추가 할 수 있습니다 기본값 이외의 이름을 지정하십시오.


-1

UIWebView에서 한 페이지에서 다른 페이지로 이동하는 시간을 최적화하려고한다고해서 실제로 이러한 페이지를로드하는 데 사용되는 Javascript를 최적화하려고하는 것은 아닙니다.

이를 위해 여기에서 언급 한 WebKit 프로파일 러를 살펴 보겠습니다.

http://www.alertdebugging.com/2009/04/29/building-a-better-javascript-profiler-with-webkit/

또 다른 방법은 높은 수준에서 시작하여 매번 전체 웹뷰를 새로 고치는 대신 AJAX 스타일 페이지로드를 사용하여로드 시간을 최소화하기 위해 해당 웹 페이지를 디자인하는 방법을 생각하는 것입니다.


-1
struct TIME {

    static var ti = mach_timebase_info()
    static var k: Double = 1
    static var mach_stamp: Double {

        if ti.denom == 0 {
            mach_timebase_info(&ti)
            k = Double(ti.numer) / Double(ti.denom) * 1e-6
        }
        return Double(mach_absolute_time()) * k
    }
    static var stamp: Double { return NSDate.timeIntervalSinceReferenceDate() * 1000 }
}

do {
    let mach_start = TIME.mach_stamp
    usleep(200000)
    let mach_diff = TIME.mach_stamp - mach_start

    let start = TIME.stamp
    usleep(200000)
    let diff = TIME.stamp - start

    print(mach_diff, diff)
}

-1

장기 실행 프로세스를 찾기 위해 어디서나 코드를 이등분하는 Swift 3 솔루션이 있습니다.

var increment: Int = 0

var incrementTime = NSDate()

struct Instrumentation {
    var title: String
    var point: Int
    var elapsedTime: Double

    init(_ title: String, _ point: Int, _ elapsedTime: Double) {
        self.title = title
        self.point = point
        self.elapsedTime = elapsedTime
    }
}

var elapsedTimes = [Instrumentation]()

func instrument(_ title: String) {
    increment += 1
    let incrementedTime = -incrementTime.timeIntervalSinceNow
    let newPoint = Instrumentation(title, increment, incrementedTime)
    elapsedTimes.append(newPoint)
    incrementTime = NSDate()
}

사용법 :-

instrument("View Did Appear")

print("ELAPSED TIMES \(elapsedTimes)")

샘플 출력 :-

삭제 된 시간 [MyApp.SomeViewController.Instrumentation (제목 : "시작보기로드 실패", 포인트 : 1, 경과 시간 : 0.040504038333892822), MyApp.SomeViewController.Instrumentation (제목 : "Subviews 추가 완료", 포인트 : 2, 경과 시간 : 0.010585010051727295) MyApp.SomeViewController.Instrumentation (제목 : "보기가 나타남", 포인트 : 3, 경과 시간 : 0.56564098596572876)]


-1

많은 답변이 이상하고 실제로 밀리 초 (그러나 초 또는 다른 것)로 결과를 제공하지는 않습니다.

여기 내가 MS (MILLISECONDS)를 얻는 데 사용하는 것 :

빠른:

let startTime = NSDate().timeIntervalSince1970 * 1000

// your Swift code

let endTimeMinusStartTime = NSDate().timeIntervalSince1970 * 1000 - startTime
print("time code execution \(endTimeMinStartTime) ms")

목표 -C :

double startTime = [[NSDate date] timeIntervalSince1970] * 1000.0;

// your Objective-C code

double endTimeMinusStartTime = [[NSDate date] timeIntervalSince1970] * 1000.0 - startTime;
printf("time code execution %f ms\n", endTimeMinusStartTime );

-1

Swift 4의 경우 수업에 대리인으로 추가하십시오.

public protocol TimingDelegate: class {
    var _TICK: Date?{ get set }
}

extension TimingDelegate {
    var TICK: Date {
        _TICK = Date()
        return(_TICK)!
     }

    func TOCK(message: String)  {

        if (_TICK == nil){
            print("Call 'TICK' first!")
        }

        if (message == ""){
            print("\(Date().timeIntervalSince(_TICK!))")
        }
        else{
            print("\(message): \(Date().timeIntervalSince(_TICK!))")
        }
    }
}

우리 수업에 추가하십시오 :

class MyViewcontroller: UIViewController, TimingDelegate

그런 다음 수업에 추가하십시오.

var _TICK: Date?

시간을 내고 싶다면 다음으로 시작하십시오.

TICK

그리고 끝으로 :

TOCK("Timing the XXX routine")

답변과 의견을 읽었습니까? 이것을 위해 날짜를 사용하지 마십시오!
matt
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.